<commit_before>"use strict";

import debug from 'debug'
import FileServer from './file-server'
import FileWatcher from './file-watcher'
import EventServer from './event-server'
import AlloyCompiler from './alloy-compiler'
import { isAppJS } from '../util'

const ____ = debug('faster-titanium:MainProcess')
const ___x = debug('faster-titanium:MainProcess:error')

const wait = t => new Promise(y => setTimeout(y, t))

/**
 * main process
 */
export default class MainProcess {

    /**
     * @param {string} projDir
     * @param {Object} [options={}]
     * @param {number} [options.minIntervalSec=3] minimum interval seconds to broadcast
     */
    constructor(projDir, options = {}) {

        const { fPort, ePort, host, minIntervalSec } = options

        /** @type {string} project dir */
        this.projDir = projDir

        /** @type {number} unix time(msec) of last broadcast*/
        this.lastBroadcast = 0

        /** @type {number} minimum interval to broadcast */
        this.minIntervalSec = parseInt(minIntervalSec, 10) || 3

        /** @type {FileServer} */
        this.fServer = new FileServer(this.projDir, fPort, host)

        /** @type {FileWatcher} */
        this.watcher = new FileWatcher(this.projDir)

        /** @type {EventServer} */
        this.eServer = new EventServer(ePort, host)

        /** @type {AlloyCompiler} */
        this.compiler = new AlloyCompiler(this.projDir)

        this.registerListeners()
    }

    /**
     * register event listeners.
     * called only once in constructor
     * @private
     */
    registerListeners() {

        this.fServer.on('error', ___x)
        this.eServer.on('error', ___x)
        this.watcher.on('error', ___x)

        this.watcher.on('change', ::this.onResourceFileChanged)
        this.watcher.on('change:alloy', ::this.onAlloyFileChanged)
        this.fServer.on('got-kill-message', ::this.end)
        this.fServer.on('got-reload-message', ::this.broadcastReload)
    }

    /**
     * start fileserver and eventserver
     * @return {Promise}
     * @public
     */
    start() {
        ____(`starting servers`)
        return Promise.all([
            this.fServer.listen(),
            this.eServer.listen()
        ]).catch(___x)
    }

    /**
     * close servers and stop watching
     * @public
     */
    end() {
        ____(`terminating servers`)
        Promise.all([
            this.fServer.close(),
            this.eServer.close(),
            this.watcher.close()
        ])
    }

    /** @type {number} */
    get timeToBroadcast() {
        const time = new Date().getTime()
        return Math.max(0, this.lastBroadcast + this.minIntervalSec * 1000 - time)
    }

    /**
     * called when files in Resources directory changed
     * @param {string} path
     * @private
     */
    onResourceFileChanged(path) {
        ____(`changed: ${path}`)

        if (isAppJS(this.projDir, path)) {
            this.fServer.clearAppJSCache()
        }
        this.broadcastReload()
    }

    /**
     * called when files in app directory (Alloy project) changed
     * @param {string} path
     * @private
     */
    onAlloyFileChanged(path) {
        if (path === this.projDir + '/app/alloy.js') {
            this.fServer.clearAppJSCache()
        }
        ____(`changed:alloy ${path}`)

        this.compileAlloy(path)
    }

    /**
     * send reload message to all connected clients
     * @private
     */
    broadcastReload() {
        if (this.timeToBroadcast > 0) {
            return ____(`Broadcasting suppressed. Available in ${this.timeToBroadcast} msec.`)
        }

        this.eServer.broadcast({event: 'reload'})
        this.lastBroadcast = new Date().getTime()
    }

    /**
     * compile alloy when one of the files in alloy changes
     * @param {string} path
     * @todo support for non-ios|android OS
     * @private
     */
    compileAlloy(path) {

        this.watcher.unwatchResources()

        this.compiler.compile(path)

            .then(x => wait(this.timeToBroadcast))
            .then(x => {
                this.broadcastReload(path)
                this.watcher.watchResources()
            })
            .catch(___x)
    }
}
<commit_msg>sync timing to broadcast<commit_after>"use strict";

import debug from 'debug'
import FileServer from './file-server'
import FileWatcher from './file-watcher'
import EventServer from './event-server'
import AlloyCompiler from './alloy-compiler'
import { isAppJS } from '../util'

const ____ = debug('faster-titanium:MainProcess')
const ___x = debug('faster-titanium:MainProcess:error')

const wait = t => new Promise(y => setTimeout(y, t))

/**
 * main process
 */
export default class MainProcess {

    /**
     * @param {string} projDir
     * @param {Object} [options={}]
     * @param {number} [options.minIntervalSec=3] minimum interval seconds to broadcast
     */
    constructor(projDir, options = {}) {

        const { fPort, ePort, host, minIntervalSec } = options

        /** @type {string} project dir */
        this.projDir = projDir
        /** @type {number} @private */
        this.reservedBroadcasts = 0
        /** @type {FileServer} */
        this.fServer = new FileServer(this.projDir, fPort, host)
        /** @type {FileWatcher} */
        this.watcher = new FileWatcher(this.projDir)
        /** @type {EventServer} */
        this.eServer = new EventServer(ePort, host)
        /** @type {AlloyCompiler} */
        this.compiler = new AlloyCompiler(this.projDir)

        this.registerListeners()
    }

    /**
     * register event listeners.
     * called only once in constructor
     * @private
     */
    registerListeners() {

        this.fServer.on('error', ___x)
        this.eServer.on('error', ___x)
        this.watcher.on('error', ___x)

        this.watcher.on('change', ::this.onResourceFileChanged)
        this.watcher.on('change:alloy', ::this.onAlloyFileChanged)
        this.fServer.on('got-kill-message', ::this.end)
        this.fServer.on('got-reload-message', ::this.broadcastReload)
    }

    /**
     * start fileserver and eventserver
     * @return {Promise}
     * @public
     */
    start() {
        ____(`starting servers`)
        return Promise.all([
            this.fServer.listen(),
            this.eServer.listen()
        ]).catch(___x)
    }

    /**
     * close servers and stop watching
     * @public
     */
    end() {
        ____(`terminating servers`)
        Promise.all([
            this.fServer.close(),
            this.eServer.close(),
            this.watcher.close()
        ])
    }

    /**
     * called when files in Resources directory changed
     * @param {string} path
     * @private
     */
    onResourceFileChanged(path) {
        ____(`changed: ${path}`)

        if (isAppJS(this.projDir, path)) {
            this.fServer.clearAppJSCache()
        }
        this.broadcastReload()
    }

    /**
     * called when files in app directory (Alloy project) changed
     * @param {string} path
     * @private
     */
    onAlloyFileChanged(path) {
        if (path === this.projDir + '/app/alloy.js') {
            this.fServer.clearAppJSCache()
        }

        ____(`changed:alloy ${path}`)

        this.willBroadcast(this.compileAlloy(path).catch(___x))
    }

    /**
     * broadcast
     */
    willBroadcast(promise) {
        this.reservedBroadcasts++
        return promise.then(x => {
            this.reservedBroadcasts--
            return this.broadcastReload(0)
        })
    }

    /**
     * send reload message to all connected clients
     * @return {Promise}
     * @private
     */
    broadcastReload(duration = 1000) {

        this.reservedBroadcasts++

        return wait(duration).then(x => {
            this.reservedBroadcasts--
            ____("this.reservedBroadcasts", this.reservedBroadcasts)

            if (this.reservedBroadcasts > 0) { return; }

            this.eServer.broadcast({event: 'reload'})
        })
    }

    /**
     * compile alloy when one of the files in alloy changes
     * @param {string} path
     * @todo support for non-ios|android OS
     * @private
     */
    compileAlloy(path) {

        this.watcher.unwatchResources()

        return this.compiler.compile(path)
            .then(x => this.watcher.watchResources())
    }
}
<|endoftext|>


<commit_before><commit_msg>Readd blog js<commit_after>/**
 * Interaction for the blog module
 */
jsBackend.Blog = {
  // init, something like a constructor
  init: function () {
    // variables
    var $title = $('#title')

    jsBackend.Blog.controls.init()

    // do meta
    if ($title.length > 0) $title.doMeta()
  }
}

jsBackend.Blog.controls = {
  currentCategory: null,

  // init, something like a constructor
  init: function () {
    // variables
    var $addCategorySubmit = $('#addCategorySubmit')
    var $addCategoryDialog = $('#addCategoryDialog')
    var $categoryTitleError = $('#categoryTitleError')
    var $categoryId = $('#categoryId')

    if ($addCategorySubmit.length > 0 && $addCategoryDialog.length > 0) {
      $addCategorySubmit.click(function () {
        // hide errors
        $categoryTitleError.hide()

        $.ajax({
          data: {
            fork: {action: 'AddCategory'},
            value: $('#categoryTitle').val()
          },
          success: function (json, textStatus) {
            if (json.code !== 200) {
              // show error if needed
              if (jsBackend.debug) window.alert(textStatus)

              // show message
              $categoryTitleError.show()
            } else {
              // add and set selected
              $categoryId.append('<option value="' + json.data.id + '">' + json.data.title + '</option>')

              // reset value
              jsBackend.Blog.controls.currentCategory = json.data.id

              // close dialog
              $addCategoryDialog.modal('hide')
            }
          }
        })
      })

      $addCategoryDialog.on('hide.bs.modal', function () {
        $categoryId.val(jsBackend.Blog.controls.currentCategory)
      })

      // bind change
      $categoryId.on('change', function (e) {
        // new category?
        if ($(this).val() === 'new_category') {
          // prevent default
          e.preventDefault()

          // open dialog
          $addCategoryDialog.modal('show')
        } else {
          // reset current category
          jsBackend.Blog.controls.currentCategory = $categoryId.val()
        }
      })
    }

    jsBackend.Blog.controls.currentCategory = $categoryId.val()
  }
}

$(jsBackend.Blog.init)
<|endoftext|>


<commit_before>const service = require('feathers-mongoose');
const { userModel, registrationPinModel } = require('./model');
const hooks = require('./hooks');
const registrationPinsHooks = require('./hooks/registrationPins');
const publicTeachersHooks = require('./hooks/publicTeachers');
const firstLoginHooks = require('./hooks/firstLogin');
const skipRegistrationHooks = require('./hooks/skipRegistration');
const { AdminUsers, UserLinkImportService, SkipRegistrationService } = require('./services');
const adminHook = require('./hooks/admin');

module.exports = function setup() {
	const app = this;

	const options = {
		Model: userModel,
		paginate: {
			default: 1000,
			max: 1000,
		},
		lean: true,
	};

	app.use('/users', service(options));

	const userService = app.service('/users');
	app.use('users/linkImport', new UserLinkImportService(userService)); // do not use hooks

	userService.hooks(hooks); // TODO: refactor

	/* publicTeachers Service */
	app.use('/publicTeachers', service({
		Model: userModel,
		paginate: {
			default: 25,
			max: 1000,
		},
		lean: true,
	}));

	const publicTeachersService = app.service('/publicTeachers');
	publicTeachersService.hooks(publicTeachersHooks);

	/* registrationPin Service */
	app.use('/registrationPins', service({
		Model: registrationPinModel,
		paginate: {
			default: 500,
			max: 5000,
		},
	}));
	const registrationPinService = app.service('/registrationPins');
	registrationPinService.hooks(registrationPinsHooks);

	const RegistrationService = require('./registration')(app);
	app.use('/registration', new RegistrationService());

	const FirstLoginService = require('./firstLogin')(app);
	app.use('/firstLogin', new FirstLoginService());
	const firstLoginService = app.service('firstLogin');
	firstLoginService.hooks(firstLoginHooks);

	const adminStudentsRoute = '/users/admin/students';
	app.use(adminStudentsRoute, new AdminUsers('student'));
	const adminStudentsService = app.service(adminStudentsRoute);
	adminStudentsService.hooks(adminHook);

	const adminTeachersRoute = '/users/admin/teachers';
	app.use(adminTeachersRoute, new AdminUsers('teacher'));
	const adminTeachersService = app.service(adminTeachersRoute);
	adminTeachersService.hooks(adminHook);

	app.use('/users/:id/skipregistration', new SkipRegistrationService());
	const skipRegistrationService = app.service(adminStudentsRoute);
	skipRegistrationService.hooks(skipRegistrationHooks);
};
<commit_msg>more descriptive url<commit_after>const service = require('feathers-mongoose');
const { userModel, registrationPinModel } = require('./model');
const hooks = require('./hooks');
const registrationPinsHooks = require('./hooks/registrationPins');
const publicTeachersHooks = require('./hooks/publicTeachers');
const firstLoginHooks = require('./hooks/firstLogin');
const skipRegistrationHooks = require('./hooks/skipRegistration');
const { AdminUsers, UserLinkImportService, SkipRegistrationService } = require('./services');
const adminHook = require('./hooks/admin');

module.exports = function setup() {
	const app = this;

	const options = {
		Model: userModel,
		paginate: {
			default: 1000,
			max: 1000,
		},
		lean: true,
	};

	app.use('/users', service(options));

	const userService = app.service('/users');
	app.use('users/linkImport', new UserLinkImportService(userService)); // do not use hooks

	userService.hooks(hooks); // TODO: refactor

	/* publicTeachers Service */
	app.use('/publicTeachers', service({
		Model: userModel,
		paginate: {
			default: 25,
			max: 1000,
		},
		lean: true,
	}));

	const publicTeachersService = app.service('/publicTeachers');
	publicTeachersService.hooks(publicTeachersHooks);

	/* registrationPin Service */
	app.use('/registrationPins', service({
		Model: registrationPinModel,
		paginate: {
			default: 500,
			max: 5000,
		},
	}));
	const registrationPinService = app.service('/registrationPins');
	registrationPinService.hooks(registrationPinsHooks);

	const RegistrationService = require('./registration')(app);
	app.use('/registration', new RegistrationService());

	const FirstLoginService = require('./firstLogin')(app);
	app.use('/firstLogin', new FirstLoginService());
	const firstLoginService = app.service('firstLogin');
	firstLoginService.hooks(firstLoginHooks);

	const adminStudentsRoute = '/users/admin/students';
	app.use(adminStudentsRoute, new AdminUsers('student'));
	const adminStudentsService = app.service(adminStudentsRoute);
	adminStudentsService.hooks(adminHook);

	const adminTeachersRoute = '/users/admin/teachers';
	app.use(adminTeachersRoute, new AdminUsers('teacher'));
	const adminTeachersService = app.service(adminTeachersRoute);
	adminTeachersService.hooks(adminHook);

	app.use('/users/:userid/skipregistration', new SkipRegistrationService());
	const skipRegistrationService = app.service('/users/:userid/skipregistration');
	skipRegistrationService.hooks(skipRegistrationHooks);
};
<|endoftext|>


<commit_before>import { connect } from 'react-redux'
import { IndexRoute, Route, Router, browserHistory } from 'react-router'
export { IndexRoute, Route, Router, connect, browserHistory }
<commit_msg>Remove route helper<commit_after><|endoftext|>


<commit_before>const modifiers = require('../modifiers/base');
const utils     = require('../utils');
const raf       = require('../utils/raf');

function init (scope) {
  const {
    Interaction,
    defaults,
  } = scope;

  Interaction.signals.on('new', function (interaction) {
    interaction.simulations.inertia = {
      active     : false,
      smoothEnd  : false,
      allowResume: false,

      startEvent: null,
      upCoords  : {},

      xe: 0, ye: 0,
      sx: 0, sy: 0,

      t0: 0,
      vx0: 0, vys: 0,
      duration: 0,

      lambda_v0: 0,
      one_ve_v0: 0,
      i  : null,
    };
  });

  Interaction.signals.on('up'  , arg => release(arg, scope));
  Interaction.signals.on('down', arg => resume (arg, scope));
  Interaction.signals.on('stop', arg => stop   (arg, scope));

  defaults.perAction.inertia = {
    enabled          : false,
    resistance       : 10,    // the lambda in exponential decay
    minSpeed         : 100,   // target speed must be above this for inertia to start
    endSpeed         : 10,    // the speed at which inertia is slow enough to stop
    allowResume      : true,  // allow resuming an action in inertia phase
    smoothEndDuration: 300,   // animate to snap/restrict endOnly if there's no inertia
  };
}

function resume ({ interaction, event, pointer, eventTarget }, scope) {
  const status = interaction.simulations.inertia;

  // Check if the down event hits the current inertia target
  if (status.active) {
    let element = eventTarget;

    // climb up the DOM tree from the event target
    while (utils.is.element(element)) {

      // if interaction element is the current inertia target element
      if (element === interaction.element) {
        // stop inertia
        raf.cancel(status.i);
        status.active = false;
        interaction.simulation = null;

        // update pointers to the down event's coordinates
        interaction.updatePointer(pointer, event, eventTarget, true);
        utils.pointer.setCoords(interaction.curCoords, interaction.pointers);

        // fire appropriate signals
        const signalArg = {
          interaction,
          iEvent: resumeEvent,
        };

        scope.Interaction.signals.fire('action-resume', signalArg);

        // fire a reume event
        const resumeEvent = new scope.InteractEvent(
          interaction, event, interaction.prepared.name, 'inertiaresume', interaction.element);

        interaction.target.fire(resumeEvent);
        interaction.prevEvent = resumeEvent;
        modifiers.resetStatuses(interaction.modifierStatuses, scope.modifiers);

        utils.pointer.copyCoords(interaction.prevCoords, interaction.curCoords);
        break;
      }

      element = utils.dom.parentNode(element);
    }
  }
}

function release ({ interaction, event }, scope) {
  const status = interaction.simulations.inertia;

  if (!interaction.interacting() || (interaction.simulation && interaction.simulation.active)) {
    return;
  }

  const options = getOptions(interaction);

  const now = new Date().getTime();
  const pointerSpeed = interaction.pointerDelta.client.speed;

  let smoothEnd = false;
  let modifierResult;

  // check if inertia should be started
  const inertiaPossible = (options && options.enabled
                     && interaction.prepared.name !== 'gesture'
                     && event !== status.startEvent);

  const inertia = (inertiaPossible
    && (now - interaction.curCoords.timeStamp) < 50
    && pointerSpeed > options.minSpeed
    && pointerSpeed > options.endSpeed);

  const modifierArg = {
    interaction,
    pageCoords: utils.extend({}, interaction.curCoords.page),
    statuses: {},
    preEnd: true,
    requireEndOnly: true,
  };

  // smoothEnd
  if (inertiaPossible && !inertia) {
    modifiers.resetStatuses(modifierArg.statuses, scope.modifiers);

    modifierResult = modifiers.setAll(modifierArg, scope.modifiers);

    if (modifierResult.shouldMove && modifierResult.locked) {
      smoothEnd = true;
    }
  }

  if (!(inertia || smoothEnd)) { return; }

  utils.pointer.copyCoords(status.upCoords, interaction.curCoords);

  interaction.pointers[0] = status.startEvent = new scope.InteractEvent(
    interaction, event, interaction.prepared.name, 'inertiastart', interaction.element);

  status.t0 = now;

  status.active = true;
  status.allowResume = options.allowResume;
  interaction.simulation = status;

  interaction.target.fire(status.startEvent);

  if (inertia) {
    status.vx0 = interaction.pointerDelta.client.vx;
    status.vy0 = interaction.pointerDelta.client.vy;
    status.v0 = pointerSpeed;

    calcInertia(interaction, status);

    utils.extend(modifierArg.pageCoords, interaction.curCoords.page);

    modifierArg.pageCoords.x += status.xe;
    modifierArg.pageCoords.y += status.ye;

    modifiers.resetStatuses(modifierArg.statuses, scope.modifiers);

    modifierResult = modifiers.setAll(modifierArg, scope.modifiers);

    status.modifiedXe += modifierResult.dx;
    status.modifiedYe += modifierResult.dy;

    status.i = raf.request(() => inertiaTick(interaction));
  }
  else {
    status.smoothEnd = true;
    status.xe = modifierResult.dx;
    status.ye = modifierResult.dy;

    status.sx = status.sy = 0;

    status.i = raf.request(() => smothEndTick(interaction));
  }
}

function stop ({ interaction }) {
  const status = interaction.simulations.inertia;

  if (status.active) {
    raf.cancel(status.i);
    status.active = false;
    interaction.simulation = null;
  }
}

function calcInertia (interaction, status) {
  const options = getOptions(interaction);
  const lambda = options.resistance;
  const inertiaDur = -Math.log(options.endSpeed / status.v0) / lambda;

  status.x0 = interaction.prevEvent.pageX;
  status.y0 = interaction.prevEvent.pageY;
  status.t0 = status.startEvent.timeStamp / 1000;
  status.sx = status.sy = 0;

  status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
  status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
  status.te = inertiaDur;

  status.lambda_v0 = lambda / status.v0;
  status.one_ve_v0 = 1 - options.endSpeed / status.v0;
}

function inertiaTick (interaction) {
  updateInertiaCoords(interaction);
  utils.pointer.setCoordDeltas(interaction.pointerDelta, interaction.prevCoords, interaction.curCoords);

  const status = interaction.simulations.inertia;
  const options = getOptions(interaction);
  const lambda = options.resistance;
  const t = new Date().getTime() / 1000 - status.t0;

  if (t < status.te) {

    const progress =  1 - (Math.exp(-lambda * t) - status.lambda_v0) / status.one_ve_v0;

    if (status.modifiedXe === status.xe && status.modifiedYe === status.ye) {
      status.sx = status.xe * progress;
      status.sy = status.ye * progress;
    }
    else {
      const quadPoint = utils.getQuadraticCurvePoint(
        0, 0,
        status.xe, status.ye,
        status.modifiedXe, status.modifiedYe,
        progress);

      status.sx = quadPoint.x;
      status.sy = quadPoint.y;
    }

    interaction.doMove();

    status.i = raf.request(() => inertiaTick(interaction));
  }
  else {
    status.sx = status.modifiedXe;
    status.sy = status.modifiedYe;

    interaction.doMove();
    interaction.end(status.startEvent);
    status.active = false;
    interaction.simulation = null;
  }

  utils.pointer.copyCoords(interaction.prevCoords, interaction.curCoords);
}

function smothEndTick (interaction) {
  updateInertiaCoords(interaction);

  const status = interaction.simulations.inertia;
  const t = new Date().getTime() - status.t0;
  const { smoothEndDuration: duration } = getOptions(interaction);

  if (t < duration) {
    status.sx = utils.easeOutQuad(t, 0, status.xe, duration);
    status.sy = utils.easeOutQuad(t, 0, status.ye, duration);

    interaction.pointerMove(status.startEvent, status.startEvent);

    status.i = raf.request(() => smothEndTick(interaction));
  }
  else {
    status.sx = status.xe;
    status.sy = status.ye;

    interaction.pointerMove(status.startEvent, status.startEvent);
    interaction.end(status.startEvent);

    status.smoothEnd =
      status.active = false;
    interaction.simulation = null;
  }
}

function updateInertiaCoords (interaction) {
  const status = interaction.simulations.inertia;

  // return if inertia isn't running
  if (!status.active) { return; }

  const pageUp   = status.upCoords.page;
  const clientUp = status.upCoords.client;

  utils.pointer.setCoords(interaction.curCoords, [ {
    pageX  : pageUp.x   + status.sx,
    pageY  : pageUp.y   + status.sy,
    clientX: clientUp.x + status.sx,
    clientY: clientUp.y + status.sy,
  } ]);
}

function getOptions ({ target, prepared }) {
  return target && target.options && prepared.name && target.options[prepared.name].inertia;
}

module.exports = {
  init,
  calcInertia,
  inertiaTick,
  smothEndTick,
  updateInertiaCoords,
};
<commit_msg>inertia: use doMove instead of poniterMove<commit_after>const modifiers = require('../modifiers/base');
const utils     = require('../utils');
const raf       = require('../utils/raf');

function init (scope) {
  const {
    Interaction,
    defaults,
  } = scope;

  Interaction.signals.on('new', function (interaction) {
    interaction.simulations.inertia = {
      active     : false,
      smoothEnd  : false,
      allowResume: false,

      startEvent: null,
      upCoords  : {},

      xe: 0, ye: 0,
      sx: 0, sy: 0,

      t0: 0,
      vx0: 0, vys: 0,
      duration: 0,

      lambda_v0: 0,
      one_ve_v0: 0,
      i  : null,
    };
  });

  Interaction.signals.on('up'  , arg => release(arg, scope));
  Interaction.signals.on('down', arg => resume (arg, scope));
  Interaction.signals.on('stop', arg => stop   (arg, scope));

  defaults.perAction.inertia = {
    enabled          : false,
    resistance       : 10,    // the lambda in exponential decay
    minSpeed         : 100,   // target speed must be above this for inertia to start
    endSpeed         : 10,    // the speed at which inertia is slow enough to stop
    allowResume      : true,  // allow resuming an action in inertia phase
    smoothEndDuration: 300,   // animate to snap/restrict endOnly if there's no inertia
  };
}

function resume ({ interaction, event, pointer, eventTarget }, scope) {
  const status = interaction.simulations.inertia;

  // Check if the down event hits the current inertia target
  if (status.active) {
    let element = eventTarget;

    // climb up the DOM tree from the event target
    while (utils.is.element(element)) {

      // if interaction element is the current inertia target element
      if (element === interaction.element) {
        // stop inertia
        raf.cancel(status.i);
        status.active = false;
        interaction.simulation = null;

        // update pointers to the down event's coordinates
        interaction.updatePointer(pointer, event, eventTarget, true);
        utils.pointer.setCoords(interaction.curCoords, interaction.pointers);

        // fire appropriate signals
        const signalArg = {
          interaction,
          iEvent: resumeEvent,
        };

        scope.Interaction.signals.fire('action-resume', signalArg);

        // fire a reume event
        const resumeEvent = new scope.InteractEvent(
          interaction, event, interaction.prepared.name, 'inertiaresume', interaction.element);

        interaction.target.fire(resumeEvent);
        interaction.prevEvent = resumeEvent;
        modifiers.resetStatuses(interaction.modifierStatuses, scope.modifiers);

        utils.pointer.copyCoords(interaction.prevCoords, interaction.curCoords);
        break;
      }

      element = utils.dom.parentNode(element);
    }
  }
}

function release ({ interaction, event }, scope) {
  const status = interaction.simulations.inertia;

  if (!interaction.interacting() || (interaction.simulation && interaction.simulation.active)) {
    return;
  }

  const options = getOptions(interaction);

  const now = new Date().getTime();
  const pointerSpeed = interaction.pointerDelta.client.speed;

  let smoothEnd = false;
  let modifierResult;

  // check if inertia should be started
  const inertiaPossible = (options && options.enabled
                     && interaction.prepared.name !== 'gesture'
                     && event !== status.startEvent);

  const inertia = (inertiaPossible
    && (now - interaction.curCoords.timeStamp) < 50
    && pointerSpeed > options.minSpeed
    && pointerSpeed > options.endSpeed);

  const modifierArg = {
    interaction,
    pageCoords: utils.extend({}, interaction.curCoords.page),
    statuses: {},
    preEnd: true,
    requireEndOnly: true,
  };

  // smoothEnd
  if (inertiaPossible && !inertia) {
    modifiers.resetStatuses(modifierArg.statuses, scope.modifiers);

    modifierResult = modifiers.setAll(modifierArg, scope.modifiers);

    if (modifierResult.shouldMove && modifierResult.locked) {
      smoothEnd = true;
    }
  }

  if (!(inertia || smoothEnd)) { return; }

  utils.pointer.copyCoords(status.upCoords, interaction.curCoords);

  interaction.pointers[0] = status.startEvent = new scope.InteractEvent(
    interaction, event, interaction.prepared.name, 'inertiastart', interaction.element);

  status.t0 = now;

  status.active = true;
  status.allowResume = options.allowResume;
  interaction.simulation = status;

  interaction.target.fire(status.startEvent);

  if (inertia) {
    status.vx0 = interaction.pointerDelta.client.vx;
    status.vy0 = interaction.pointerDelta.client.vy;
    status.v0 = pointerSpeed;

    calcInertia(interaction, status);

    utils.extend(modifierArg.pageCoords, interaction.curCoords.page);

    modifierArg.pageCoords.x += status.xe;
    modifierArg.pageCoords.y += status.ye;

    modifiers.resetStatuses(modifierArg.statuses, scope.modifiers);

    modifierResult = modifiers.setAll(modifierArg, scope.modifiers);

    status.modifiedXe += modifierResult.dx;
    status.modifiedYe += modifierResult.dy;

    status.i = raf.request(() => inertiaTick(interaction));
  }
  else {
    status.smoothEnd = true;
    status.xe = modifierResult.dx;
    status.ye = modifierResult.dy;

    status.sx = status.sy = 0;

    status.i = raf.request(() => smothEndTick(interaction));
  }
}

function stop ({ interaction }) {
  const status = interaction.simulations.inertia;

  if (status.active) {
    raf.cancel(status.i);
    status.active = false;
    interaction.simulation = null;
  }
}

function calcInertia (interaction, status) {
  const options = getOptions(interaction);
  const lambda = options.resistance;
  const inertiaDur = -Math.log(options.endSpeed / status.v0) / lambda;

  status.x0 = interaction.prevEvent.pageX;
  status.y0 = interaction.prevEvent.pageY;
  status.t0 = status.startEvent.timeStamp / 1000;
  status.sx = status.sy = 0;

  status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
  status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
  status.te = inertiaDur;

  status.lambda_v0 = lambda / status.v0;
  status.one_ve_v0 = 1 - options.endSpeed / status.v0;
}

function inertiaTick (interaction) {
  updateInertiaCoords(interaction);
  utils.pointer.setCoordDeltas(interaction.pointerDelta, interaction.prevCoords, interaction.curCoords);

  const status = interaction.simulations.inertia;
  const options = getOptions(interaction);
  const lambda = options.resistance;
  const t = new Date().getTime() / 1000 - status.t0;

  if (t < status.te) {

    const progress =  1 - (Math.exp(-lambda * t) - status.lambda_v0) / status.one_ve_v0;

    if (status.modifiedXe === status.xe && status.modifiedYe === status.ye) {
      status.sx = status.xe * progress;
      status.sy = status.ye * progress;
    }
    else {
      const quadPoint = utils.getQuadraticCurvePoint(
        0, 0,
        status.xe, status.ye,
        status.modifiedXe, status.modifiedYe,
        progress);

      status.sx = quadPoint.x;
      status.sy = quadPoint.y;
    }

    interaction.doMove();

    status.i = raf.request(() => inertiaTick(interaction));
  }
  else {
    status.sx = status.modifiedXe;
    status.sy = status.modifiedYe;

    interaction.doMove();
    interaction.end(status.startEvent);
    status.active = false;
    interaction.simulation = null;
  }

  utils.pointer.copyCoords(interaction.prevCoords, interaction.curCoords);
}

function smothEndTick (interaction) {
  updateInertiaCoords(interaction);

  const status = interaction.simulations.inertia;
  const t = new Date().getTime() - status.t0;
  const { smoothEndDuration: duration } = getOptions(interaction);

  if (t < duration) {
    status.sx = utils.easeOutQuad(t, 0, status.xe, duration);
    status.sy = utils.easeOutQuad(t, 0, status.ye, duration);

    interaction.doMove();

    status.i = raf.request(() => smothEndTick(interaction));
  }
  else {
    status.sx = status.xe;
    status.sy = status.ye;

    interaction.doMove();
    interaction.end(status.startEvent);

    status.smoothEnd =
      status.active = false;
    interaction.simulation = null;
  }
}

function updateInertiaCoords (interaction) {
  const status = interaction.simulations.inertia;

  // return if inertia isn't running
  if (!status.active) { return; }

  const pageUp   = status.upCoords.page;
  const clientUp = status.upCoords.client;

  utils.pointer.setCoords(interaction.curCoords, [ {
    pageX  : pageUp.x   + status.sx,
    pageY  : pageUp.y   + status.sy,
    clientX: clientUp.x + status.sx,
    clientY: clientUp.y + status.sy,
  } ]);
}

function getOptions ({ target, prepared }) {
  return target && target.options && prepared.name && target.options[prepared.name].inertia;
}

module.exports = {
  init,
  calcInertia,
  inertiaTick,
  smothEndTick,
  updateInertiaCoords,
};
<|endoftext|>


<commit_before>/* global tsUtils, BigNumber */
(function () {
    'use strict';

    const AVAILABLE_VALIDATORS = [
        'gt',
        'gte',
        'lt',
        'lte',
        'length',
        'integer',
        'precision',
        'byteLt',
        'byteLte',
        'byteGt',
        'byteGte',
        'number',
        'asset',
        'compare',
        'alias',
        'address',
        'wavesAddress',
        'outerBlockchains',
        'anyAddress',
        'pattern',
        'custom'
    ];

    const PATTERNS = {
        NUMBER: '\\d*\\.?\\d*', // TODO Add locale separators
        INTEGER: '\\d*'
    };

    /**
     *
     * @param {app.utils} utils
     * @param {ValidateService} validateService
     * @param {app.utils.decorators} decorators
     */
    const directive = function (utils, validateService, decorators, $rootScope, $compile) {
        return {
            require: 'ngModel',
            priority: 10000,
            /**
             * @param {JQuery} $input
             * @param {object} $attrs
             * @return {*}
             */
            compile: function ($input, $attrs) {

                /**
                 * $input can be both <input> and <w-input>, in the latter case we should ignore validation
                 */
                if ($input.get(0).tagName !== 'INPUT' && $input.get(0).tagName !== 'TEXTAREA') {
                    return null;
                }

                return function ($scope, $input, $compiledAttrs, $ngModel) {

                    class Validate {

                        constructor() {
                            this._validators = Object.create(null);
                            /**
                             * @type {Signal}
                             * @private
                             */
                            this._validatorsReady = new tsUtils.Signal();
                            this._ready = false;

                            this._validatorsReady.once(() => {
                                this._ready = true;
                            });

                            this._createValidatorList();
                            $scope.$watch($attrs.ngModel, () => this._validate());
                        }

                        _addInputPattern(pattern) {
                            const create = () => {
                                const patternName = Validate._getAttrName('pattern');
                                if (!$attrs[patternName]) {
                                    $attrs[patternName] = pattern;
                                    this._createValidator('pattern');
                                }
                            };

                            if (this._ready) {
                                create();
                            } else {
                                this._validatorsReady.once(create);
                            }
                        }

                        /**
                         * @param validators
                         * @private
                         */
                        _applyValidators(validators) {
                            validators.forEach(({ name, handler }) => {
                                const result = handler($ngModel.$modelValue, $ngModel.$viewValue);

                                switch (typeof result) {
                                    case 'boolean':
                                        $ngModel.$setValidity(name, result);
                                        break;
                                    case 'object':
                                        if (result && typeof result.then === 'function') {
                                            $ngModel.$setValidity(`pending-${name}`, false);
                                            const onEnd = () => {
                                                $ngModel.$setValidity(`pending-${name}`, true);
                                            };
                                            utils.when(result).then(() => {
                                                $ngModel.$setValidity(name, true);
                                                onEnd();
                                            }, () => {
                                                $ngModel.$setValidity(name, false);
                                                onEnd();
                                            });
                                        } else {
                                            throw new Error('Wrong validation result!');
                                        }
                                        break;
                                    default:
                                        throw new Error('Wrong validation result!');
                                }
                            });
                        }

                        /**
                         * @private
                         */
                        @decorators.async()
                        _validate() {
                            this._applyValidators(Object.keys(this._validators).map((name) => this._validators[name]));
                            $scope.$apply();
                        }

                        /**
                         * @param targetName
                         * @private
                         */
                        _validateByName(targetName) {
                            this._applyValidators([this._validators[targetName]].filter(Boolean));
                        }

                        /**
                         * @private
                         */
                        _createValidatorList() {
                            AVAILABLE_VALIDATORS.filter(Validate._hasValidator).forEach((name) => {
                                this._createValidator(name);
                            });

                            this._validatorsReady.dispatch();
                        }

                        /**
                         * @param name
                         * @private
                         */
                        _createValidator(name) {

                            if (this._validators[name]) {
                                throw new Error(`Duplicate validator! ${name}`);
                            }

                            switch (name) {
                                case 'asset':
                                    this._validators[name] = this._createAssetValidator(name);
                                    break;
                                case 'compare':
                                    this._validators[name] = this._createCompareValidator(name);
                                    break;
                                case 'custom':
                                    this._validators[name] = this._createCustomValidator(name);
                                    break;
                                case 'number':
                                    this._validators[name] = this._createBigNumberValidator(name);
                                    break;
                                case 'alias':
                                case 'address':
                                case 'wavesAddress':
                                    this._validators[name] = this._createAddressValidator(name);
                                    break;
                                case 'integer':
                                    this._validators[name] = this._createIntegerValidator(name);
                                    break;
                                case 'anyAddress':
                                    this._validators[name] = this._createAnyAddressValidator(name);
                                    break;
                                case 'outerBlockchains':
                                    this._validators[name] = this._createOuterBlockchainsValidator(name);
                                    break;
                                case 'pattern':
                                    this._validators[name] = this._createPatternValidator(name);
                                    break;
                                case 'byteLt':
                                case 'byteLte':
                                    this._validators[name] = this._createByteValidator(name);
                                    break;
                                default:
                                    this._validators[name] = this._createSimpleValidator(name);
                            }

                            if (this._validators[name].parser) {
                                $ngModel.$parsers.unshift(this._validators[name].parser);
                            }

                            if (this._validators[name].formatter) {
                                $ngModel.$formatters.push(this._validators[name].formatter);
                            }

                            return this._validators[name];
                        }

                        _createCustomValidator(name) {

                            const validator = {
                                name,
                                value: null,
                                handler: function () {
                                    if (validator.value == null) {
                                        return true;
                                    } else if (typeof validator.value === 'function') {
                                        return validator.value();
                                    } else {
                                        return !!validator.value;
                                    }
                                }
                            };

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createIntegerValidator(name) {
                            this._addInputPattern(PATTERNS.INTEGER);

                            return {
                                name,
                                value: null,
                                handler: (modelValue) => {
                                    try {
                                        const num = Validate._toBigNumber(modelValue);
                                        return !modelValue || num.round(0).eq(Validate._toBigNumber(modelValue));
                                    } catch (e) {
                                        return false;
                                    }
                                }
                            };
                        }

                        _createOuterBlockchainsValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                handler: function (address) {
                                    return validateService.outerBlockchains(address, validator.value);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (data) => {
                                    value = Validate._toAssetId(data);
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createAnyAddressValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                handler: function (address) {
                                    return validateService.anyAddress(address, validator.value);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (data) => {
                                    value = Validate._toAssetId(data);
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createCompareValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                $compare: null,
                                $compareHandler: () => {
                                    this._validateByName(name);
                                    $scope.$apply();
                                },
                                handler: (value) => {
                                    return value === validator.$compare.val();
                                },
                                destroy: function () {
                                    this.$compare.off('input', this.$compareHandler);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (val) => {
                                    if (utils.isNotEqualValue(value, val)) {
                                        if (validator.$compare) {
                                            validator.$compare.off('input', validator.$compareHandler);
                                        }
                                        validator.$compare = $input.closest('form').find(`input[name="${val}"]`);
                                        validator.$compare.on('input', validator.$compareHandler);
                                        value = val;
                                    }
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createPatternValidator(name) {
                            let value;
                            let parserWorkedBeforeInputEvent = false;

                            function getCorrespondingToPatternPartOf(value) {
                                const correspondingParts = validator.value.exec(value) || [];

                                return correspondingParts[0] || '';
                            }

                            const validator = {
                                name,
                                value: null,
                                handler: () => {
                                    return true; // TODO
                                },
                                parser: (value) => {
                                    const correspondingToPatternPartOfInput = getCorrespondingToPatternPartOf(value);

                                    if (correspondingToPatternPartOfInput !== value) {
                                        Validate._replaceInputValue(correspondingToPatternPartOfInput);
                                    }

                                    parserWorkedBeforeInputEvent = true;

                                    return correspondingToPatternPartOfInput;
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (val) => {
                                    value = new RegExp(val);
                                }
                            });

                            // Once there is empty or invalid value in an input, parsers do not always run, thus there
                            // is a need for clearing the wrong state of the input.
                            $input.on('input', () => {
                                if (parserWorkedBeforeInputEvent) {
                                    parserWorkedBeforeInputEvent = false;
                                } else {
                                    Validate._replaceInputValue(
                                        getCorrespondingToPatternPartOf($input.val())
                                    );
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        /**
                         *
                         * @param name
                         * @return {*}
                         * @private
                         */
                        _createAssetValidator(name) {
                            const precisionValidator = this._createValidator('precision');

                            this._addInputPattern(PATTERNS.NUMBER);

                            let value = null;

                            const validator = {
                                name,
                                money: null,
                                value: null,
                                apply: () => {
                                    precisionValidator.value = validator.money.asset.precision;
                                    this._validateByName(name);
                                },
                                handler: () => {
                                    return true; // Can't write no number values! :)
                                },
                                parser: (value) => {
                                    if (value && precisionValidator.handler($ngModel.$modelValue, value)) {
                                        return Validate._toMoney(value, validator.money);
                                    } else {
                                        return null;
                                    }
                                },
                                formatter: (value) => {

                                    const viewValue = $input.val();
                                    const money = precisionValidator.handler($ngModel.$modelValue, viewValue) &&
                                        Validate._toMoney(viewValue, validator.money);

                                    if (Validate._isFocused() && (!value || (money && money.eq(value)))) {
                                        return $input.val();
                                    } else {
                                        return Validate._toString(value);
                                    }
                                }
                            };

                            $input.on('input', () => {
                                this._validateByName(precisionValidator.name);
                                $scope.$apply();
                            });

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (assetData) => {

                                    validator.money = null;

                                    if (!assetData) {
                                        return null;
                                    }

                                    value = Validate._toAssetId(assetData);
                                    if (value) {
                                        Waves.Money.fromTokens('0', value).then((money) => {
                                            if (utils.isNotEqualValue(validator.money, money)) {
                                                validator.money = money;
                                                validator.apply();
                                                $ngModel.$modelValue = validator.parser($ngModel.$viewValue);
                                            }
                                        });
                                    }
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createAddressValidator(name) {
                            const validator = {
                                name,
                                value: null,
                                handler: (value) => validateService[name](value, validator.value)
                            };
                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createByteValidator(name) {
                            const validator = this._createSimpleValidator(name);
                            const $byteScope = $rootScope.$new(true);
                            const attrs = [
                                'w-i18n-ns="app.utils"',
                                'class="byte-validator__help"',
                                'w-i18n="validators.byte.help"',
                                'params="{bytes: bytes}"'
                            ];
                            const $element = $compile(`<div ${attrs.join(' ')}></div>`)($byteScope);
                            $input.before($element);
                            $scope.$on('$destroy', () => {
                                $byteScope.$destroy();
                            });

                            const origin = validator.handler;
                            validator.handler = function (modelValue) {
                                const stringBytes = validateService.getByteFromString(modelValue);
                                $byteScope.bytes = Number(validator.value) - stringBytes;
                                $byteScope.$digest();
                                return origin(modelValue);
                            };

                            return validator;
                        }

                        /**
                         * @param name
                         * @private
                         */
                        _createSimpleValidator(name) {

                            let handler;
                            switch (name) {
                                case 'precision':
                                    handler = function (modelValue, viewValue) {
                                        let value;

                                        try {
                                            if (viewValue) {
                                                value = utils.parseNiceNumber(viewValue);
                                            } else {
                                                value = null;
                                            }
                                        } catch (e) {
                                            value = null;
                                        }

                                        return validateService[name](value, validator.value);
                                    };
                                    break;
                                default:
                                    handler = function (modelValue) {
                                        return validateService[name](modelValue, validator.value);
                                    };
                            }

                            const validator = {
                                name,
                                value: null,
                                handler
                            };

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createBigNumberValidator(name) {
                            this._addInputPattern(PATTERNS.NUMBER);

                            return {
                                name,
                                handler: () => true,
                                parser: (value) => {
                                    if (value === '') {
                                        return;
                                    }

                                    return Validate._toBigNumber(value);
                                },
                                formatter: Validate._toString
                            };
                        }

                        /**
                         * @param name
                         * @param validator
                         * @private
                         */
                        _listenValidatorChanges(name, validator) {
                            const attrValue = $attrs[Validate._getAttrName(name)];

                            if (Validate._hasExp(attrValue)) {
                                const exp = attrValue.replace(/{{(.*)?}}/g, '$1');

                                if (exp.indexOf('::') !== -1) {
                                    validator.value = $scope.$eval(exp.replace('::', ''));
                                } else {
                                    $scope.$watch(exp, (value) => {
                                        validator.value = value;
                                        this._validateByName(validator.name);
                                    });
                                }

                            } else {
                                validator.value = attrValue;
                            }
                        }

                        static _isFocused() {
                            return document.activeElement === $input.get(0);
                        }

                        static _toAssetId(data) {
                            if (typeof data === 'string') {
                                return data;
                            } else if (data instanceof Waves.Money) {
                                return data.asset.id;
                            } else if (data instanceof Waves.Asset) {
                                return data.id;
                            } else {
                                return null;
                            }
                        }

                        static _toString(value) {
                            if (value instanceof BigNumber) {
                                return value.toFixed();
                            } else if (value instanceof Waves.Money) {
                                return value.toFormat();
                            } else if (!value) {
                                return '';
                            } else {
                                return String(value);
                            }
                        }

                        static _toMoney(value, target) {
                            if (!target) {
                                return null;
                            } else {
                                try {
                                    return target.cloneWithTokens(utils.parseNiceNumber(value));
                                } catch (e) {
                                    return null;
                                }
                            }
                        }

                        static _toBigNumber(value) {
                            try {
                                return new BigNumber(utils.parseNiceNumber(value));
                            } catch (e) {
                                return null;
                            }
                        }

                        /**
                         * @param value
                         * @return {boolean}
                         * @private
                         */
                        static _hasExp(value) {
                            if (!value) {
                                return false;
                            }

                            const openIndex = value.indexOf('{{');
                            const closeIndex = value.indexOf('}}');
                            return openIndex !== -1 && closeIndex !== -1 && openIndex < closeIndex;
                        }

                        /**
                         * @param validatorName
                         * @return {string}
                         * @private
                         */
                        static _getAttrName(validatorName) {
                            return `wValidator${validatorName.charAt(0).toUpperCase()}${validatorName.slice(1)}`;
                        }

                        /**
                         * @param name
                         * @return {boolean}
                         * @private
                         */
                        static _hasValidator(name) {
                            return Validate._getAttrName(name) in $attrs;
                        }

                        /**
                         * @param newValue
                         * @private
                         */
                        static _replaceInputValue(newValue) {
                            $ngModel.$viewValue = newValue;
                            $ngModel.$render();
                        }

                    }

                    return new Validate();
                };
            }
        };
    };

    directive.$inject = ['utils', 'validateService', 'decorators', '$rootScope', '$compile'];

    angular.module('app.utils').directive('wValidate', directive);

})();

<commit_msg>CLIENT-496: fix validate error<commit_after>/* global tsUtils, BigNumber */
(function () {
    'use strict';

    const AVAILABLE_VALIDATORS = [
        'gt',
        'gte',
        'lt',
        'lte',
        'length',
        'integer',
        'precision',
        'byteLt',
        'byteLte',
        'byteGt',
        'byteGte',
        'number',
        'asset',
        'compare',
        'alias',
        'address',
        'wavesAddress',
        'outerBlockchains',
        'anyAddress',
        'pattern',
        'custom'
    ];

    const PATTERNS = {
        NUMBER: '\\d*\\.?\\d*', // TODO Add locale separators
        INTEGER: '\\d*'
    };

    /**
     *
     * @param {app.utils} utils
     * @param {ValidateService} validateService
     * @param {app.utils.decorators} decorators
     */
    const directive = function (utils, validateService, decorators, $rootScope, $compile) {
        return {
            require: 'ngModel',
            priority: 10000,
            /**
             * @param {JQuery} $input
             * @param {object} $attrs
             * @return {*}
             */
            compile: function ($input, $attrs) {

                /**
                 * $input can be both <input> and <w-input>, in the latter case we should ignore validation
                 */
                if ($input.get(0).tagName !== 'INPUT' && $input.get(0).tagName !== 'TEXTAREA') {
                    return null;
                }

                return function ($scope, $input, $compiledAttrs, $ngModel) {

                    class Validate {

                        constructor() {
                            this._validators = Object.create(null);
                            /**
                             * @type {Signal}
                             * @private
                             */
                            this._validatorsReady = new tsUtils.Signal();
                            this._ready = false;

                            this._validatorsReady.once(() => {
                                this._ready = true;
                            });

                            this._createValidatorList();
                            $scope.$watch($attrs.ngModel, () => this._validate());
                        }

                        _addInputPattern(pattern) {
                            const create = () => {
                                const patternName = Validate._getAttrName('pattern');
                                if (!$attrs[patternName]) {
                                    $attrs[patternName] = pattern;
                                    this._createValidator('pattern');
                                }
                            };

                            if (this._ready) {
                                create();
                            } else {
                                this._validatorsReady.once(create);
                            }
                        }

                        /**
                         * @param validators
                         * @private
                         */
                        _applyValidators(validators) {
                            validators.forEach(({ name, handler }) => {
                                const result = handler($ngModel.$modelValue, $ngModel.$viewValue);

                                switch (typeof result) {
                                    case 'boolean':
                                        $ngModel.$setValidity(name, result);
                                        break;
                                    case 'object':
                                        if (result && typeof result.then === 'function') {
                                            $ngModel.$setValidity(`pending-${name}`, false);
                                            const onEnd = () => {
                                                $ngModel.$setValidity(`pending-${name}`, true);
                                            };
                                            utils.when(result).then(() => {
                                                $ngModel.$setValidity(name, true);
                                                onEnd();
                                            }, () => {
                                                $ngModel.$setValidity(name, false);
                                                onEnd();
                                            });
                                        } else {
                                            throw new Error('Wrong validation result!');
                                        }
                                        break;
                                    default:
                                        throw new Error('Wrong validation result!');
                                }
                            });
                        }

                        /**
                         * @private
                         */
                        @decorators.async()
                        _validate() {
                            this._applyValidators(Object.keys(this._validators).map((name) => this._validators[name]));
                            $scope.$apply();
                        }

                        /**
                         * @param targetName
                         * @private
                         */
                        _validateByName(targetName) {
                            this._applyValidators([this._validators[targetName]].filter(Boolean));
                        }

                        /**
                         * @private
                         */
                        _createValidatorList() {
                            AVAILABLE_VALIDATORS.filter(Validate._hasValidator).forEach((name) => {
                                this._createValidator(name);
                            });

                            this._validatorsReady.dispatch();
                        }

                        /**
                         * @param name
                         * @private
                         */
                        _createValidator(name) {

                            if (this._validators[name]) {
                                throw new Error(`Duplicate validator! ${name}`);
                            }

                            switch (name) {
                                case 'asset':
                                    this._validators[name] = this._createAssetValidator(name);
                                    break;
                                case 'compare':
                                    this._validators[name] = this._createCompareValidator(name);
                                    break;
                                case 'custom':
                                    this._validators[name] = this._createCustomValidator(name);
                                    break;
                                case 'number':
                                    this._validators[name] = this._createBigNumberValidator(name);
                                    break;
                                case 'alias':
                                case 'address':
                                case 'wavesAddress':
                                    this._validators[name] = this._createAddressValidator(name);
                                    break;
                                case 'integer':
                                    this._validators[name] = this._createIntegerValidator(name);
                                    break;
                                case 'anyAddress':
                                    this._validators[name] = this._createAnyAddressValidator(name);
                                    break;
                                case 'outerBlockchains':
                                    this._validators[name] = this._createOuterBlockchainsValidator(name);
                                    break;
                                case 'pattern':
                                    this._validators[name] = this._createPatternValidator(name);
                                    break;
                                case 'byteLt':
                                case 'byteLte':
                                    this._validators[name] = this._createByteValidator(name);
                                    break;
                                default:
                                    this._validators[name] = this._createSimpleValidator(name);
                            }

                            if (this._validators[name].parser) {
                                $ngModel.$parsers.unshift(this._validators[name].parser);
                            }

                            if (this._validators[name].formatter) {
                                $ngModel.$formatters.push(this._validators[name].formatter);
                            }

                            return this._validators[name];
                        }

                        _createCustomValidator(name) {

                            const validator = {
                                name,
                                value: null,
                                handler: function () {
                                    if (validator.value == null) {
                                        return true;
                                    } else if (typeof validator.value === 'function') {
                                        return validator.value();
                                    } else {
                                        return !!validator.value;
                                    }
                                }
                            };

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createIntegerValidator(name) {
                            this._addInputPattern(PATTERNS.INTEGER);

                            return {
                                name,
                                value: null,
                                handler: (modelValue) => {
                                    try {
                                        const num = Validate._toBigNumber(modelValue);
                                        return !modelValue || num.round(0).eq(Validate._toBigNumber(modelValue));
                                    } catch (e) {
                                        return false;
                                    }
                                }
                            };
                        }

                        _createOuterBlockchainsValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                handler: function (address) {
                                    return validateService.outerBlockchains(address, validator.value);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (data) => {
                                    value = Validate._toAssetId(data);
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createAnyAddressValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                handler: function (address) {
                                    return validateService.anyAddress(address, validator.value);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (data) => {
                                    value = Validate._toAssetId(data);
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createCompareValidator(name) {

                            let value = null;

                            const validator = {
                                name,
                                value: null,
                                $compare: null,
                                $compareHandler: () => {
                                    this._validateByName(name);
                                    $scope.$apply();
                                },
                                handler: (value) => {
                                    return value === validator.$compare.val();
                                },
                                destroy: function () {
                                    this.$compare.off('input', this.$compareHandler);
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (val) => {
                                    if (utils.isNotEqualValue(value, val)) {
                                        if (validator.$compare) {
                                            validator.$compare.off('input', validator.$compareHandler);
                                        }
                                        validator.$compare = $input.closest('form').find(`input[name="${val}"]`);
                                        validator.$compare.on('input', validator.$compareHandler);
                                        value = val;
                                    }
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createPatternValidator(name) {
                            let value;
                            let parserWorkedBeforeInputEvent = false;

                            function getCorrespondingToPatternPartOf(value) {
                                const correspondingParts = validator.value.exec(value) || [];

                                return correspondingParts[0] || '';
                            }

                            const validator = {
                                name,
                                value: null,
                                handler: () => {
                                    return true; // TODO
                                },
                                parser: (value) => {
                                    const correspondingToPatternPartOfInput = getCorrespondingToPatternPartOf(value);

                                    if (correspondingToPatternPartOfInput !== value) {
                                        Validate._replaceInputValue(correspondingToPatternPartOfInput);
                                    }

                                    parserWorkedBeforeInputEvent = true;

                                    return correspondingToPatternPartOfInput;
                                }
                            };

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (val) => {
                                    value = new RegExp(val);
                                }
                            });

                            // Once there is empty or invalid value in an input, parsers do not always run, thus there
                            // is a need for clearing the wrong state of the input.
                            $input.on('input', () => {
                                if (parserWorkedBeforeInputEvent) {
                                    parserWorkedBeforeInputEvent = false;
                                } else {
                                    Validate._replaceInputValue(
                                        getCorrespondingToPatternPartOf($input.val())
                                    );
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        /**
                         *
                         * @param name
                         * @return {*}
                         * @private
                         */
                        _createAssetValidator(name) {
                            const precisionValidator = this._createValidator('precision');

                            this._addInputPattern(PATTERNS.NUMBER);

                            let value = null;

                            const validator = {
                                name,
                                money: null,
                                value: null,
                                apply: () => {
                                    precisionValidator.value = validator.money.asset.precision;
                                    this._validateByName(name);
                                },
                                handler: () => {
                                    return true; // Can't write no number values! :)
                                },
                                parser: (value) => {
                                    if (value && precisionValidator.handler($ngModel.$modelValue, value)) {
                                        return Validate._toMoney(value, validator.money);
                                    } else {
                                        return null;
                                    }
                                },
                                formatter: (value) => {

                                    const viewValue = $input.val();
                                    const money = precisionValidator.handler($ngModel.$modelValue, viewValue) &&
                                        Validate._toMoney(viewValue, validator.money);

                                    if (Validate._isFocused() && (!value || (money && money.eq(value)))) {
                                        return $input.val();
                                    } else {
                                        return Validate._toString(value);
                                    }
                                }
                            };

                            $input.on('input', () => {
                                this._validateByName(precisionValidator.name);
                                $scope.$apply();
                            });

                            Object.defineProperty(validator, 'value', {
                                get: () => value,
                                set: (assetData) => {

                                    validator.money = null;

                                    if (!assetData) {
                                        return null;
                                    }

                                    value = Validate._toAssetId(assetData);
                                    if (value) {
                                        Waves.Money.fromTokens('0', value).then((money) => {
                                            if (utils.isNotEqualValue(validator.money, money)) {
                                                validator.money = money;
                                                validator.apply();
                                                $ngModel.$modelValue = validator.parser($ngModel.$viewValue);
                                            }
                                        });
                                    }
                                }
                            });

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createAddressValidator(name) {
                            const validator = {
                                name,
                                value: null,
                                handler: (value) => validateService[name](value, validator.value)
                            };
                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createByteValidator(name) {
                            const validator = this._createSimpleValidator(name);
                            const $byteScope = $rootScope.$new(true);
                            const attrs = [
                                'w-i18n-ns="app.utils"',
                                'class="byte-validator__help"',
                                'w-i18n="validators.byte.help"',
                                'params="{bytes: bytes}"'
                            ];
                            const $element = $compile(`<div ${attrs.join(' ')}></div>`)($byteScope);
                            $input.before($element);
                            $scope.$on('$destroy', () => {
                                $byteScope.$destroy();
                            });

                            const origin = validator.handler;
                            validator.handler = function (modelValue) {
                                const stringBytes = validateService.getByteFromString(modelValue || '');
                                $byteScope.bytes = Number(validator.value) - stringBytes;
                                $byteScope.$digest();
                                return origin(modelValue);
                            };

                            return validator;
                        }

                        /**
                         * @param name
                         * @private
                         */
                        _createSimpleValidator(name) {

                            let handler;
                            switch (name) {
                                case 'precision':
                                    handler = function (modelValue, viewValue) {
                                        let value;

                                        try {
                                            if (viewValue) {
                                                value = utils.parseNiceNumber(viewValue);
                                            } else {
                                                value = null;
                                            }
                                        } catch (e) {
                                            value = null;
                                        }

                                        return validateService[name](value, validator.value);
                                    };
                                    break;
                                default:
                                    handler = function (modelValue) {
                                        return validateService[name](modelValue, validator.value);
                                    };
                            }

                            const validator = {
                                name,
                                value: null,
                                handler
                            };

                            this._listenValidatorChanges(name, validator);

                            return validator;
                        }

                        _createBigNumberValidator(name) {
                            this._addInputPattern(PATTERNS.NUMBER);

                            return {
                                name,
                                handler: () => true,
                                parser: (value) => {
                                    if (value === '') {
                                        return;
                                    }

                                    return Validate._toBigNumber(value);
                                },
                                formatter: Validate._toString
                            };
                        }

                        /**
                         * @param name
                         * @param validator
                         * @private
                         */
                        _listenValidatorChanges(name, validator) {
                            const attrValue = $attrs[Validate._getAttrName(name)];

                            if (Validate._hasExp(attrValue)) {
                                const exp = attrValue.replace(/{{(.*)?}}/g, '$1');

                                if (exp.indexOf('::') !== -1) {
                                    validator.value = $scope.$eval(exp.replace('::', ''));
                                } else {
                                    $scope.$watch(exp, (value) => {
                                        validator.value = value;
                                        this._validateByName(validator.name);
                                    });
                                }

                            } else {
                                validator.value = attrValue;
                            }
                        }

                        static _isFocused() {
                            return document.activeElement === $input.get(0);
                        }

                        static _toAssetId(data) {
                            if (typeof data === 'string') {
                                return data;
                            } else if (data instanceof Waves.Money) {
                                return data.asset.id;
                            } else if (data instanceof Waves.Asset) {
                                return data.id;
                            } else {
                                return null;
                            }
                        }

                        static _toString(value) {
                            if (value instanceof BigNumber) {
                                return value.toFixed();
                            } else if (value instanceof Waves.Money) {
                                return value.toFormat();
                            } else if (!value) {
                                return '';
                            } else {
                                return String(value);
                            }
                        }

                        static _toMoney(value, target) {
                            if (!target) {
                                return null;
                            } else {
                                try {
                                    return target.cloneWithTokens(utils.parseNiceNumber(value));
                                } catch (e) {
                                    return null;
                                }
                            }
                        }

                        static _toBigNumber(value) {
                            try {
                                return new BigNumber(utils.parseNiceNumber(value));
                            } catch (e) {
                                return null;
                            }
                        }

                        /**
                         * @param value
                         * @return {boolean}
                         * @private
                         */
                        static _hasExp(value) {
                            if (!value) {
                                return false;
                            }

                            const openIndex = value.indexOf('{{');
                            const closeIndex = value.indexOf('}}');
                            return openIndex !== -1 && closeIndex !== -1 && openIndex < closeIndex;
                        }

                        /**
                         * @param validatorName
                         * @return {string}
                         * @private
                         */
                        static _getAttrName(validatorName) {
                            return `wValidator${validatorName.charAt(0).toUpperCase()}${validatorName.slice(1)}`;
                        }

                        /**
                         * @param name
                         * @return {boolean}
                         * @private
                         */
                        static _hasValidator(name) {
                            return Validate._getAttrName(name) in $attrs;
                        }

                        /**
                         * @param newValue
                         * @private
                         */
                        static _replaceInputValue(newValue) {
                            $ngModel.$viewValue = newValue;
                            $ngModel.$render();
                        }

                    }

                    return new Validate();
                };
            }
        };
    };

    directive.$inject = ['utils', 'validateService', 'decorators', '$rootScope', '$compile'];

    angular.module('app.utils').directive('wValidate', directive);

})();

<|endoftext|>