<commit_before>#!/usr/bin/env node

var child_process = require('child_process');
var fs = require('fs');
var optimist = require('optimist');
var phantom = require('phantom');
var phantomOptions = { 'web-security': 'no' };

var argv = optimist
.usage('Usage: depict URL OUT_FILE [OPTIONS]')
.options('h', {
    alias: 'help',
    describe: 'Display help',
    default: false
})
.options('s', {
    alias: 'selector',
    describe: 'CSS selector',
    default: 'body'
})
.options('c', {
    alias: 'css',
    describe: 'CSS file to include in rendering',
    default: false
})
.options('H', {
    alias: 'hide-selector',
    describe: 'Hide attributes of this selector before rendering.',
    default: false
})
.options('w', {
    alias: 'browser-width',
    describe: 'Specify the desired browser width.',
    default: 1440
})
.options('d', {
    alias: 'delay',
    describe: 'Specify a delay time, in milliseconds.',
    default: 1000
})
.options('call-phantom', {
    describe: 'Whether to wait for the target page to call `callPhantom()`.',
    default: false
})
.options('call-phantom-timeout', {
    describe: 'How long to wait for the target page to call `callPhantom()`, in seconds.',
    default: 30
})
.check(function(argv) {
    if (argv._.length !== 2) {
        throw new Error('URL and OUT_FILE must be given.');
        process.exit(1);
    }
    if (argv['call-phantom-timeout'] !== 30 && !argv['call-phantom']) {
        throw new Error('--call-phantom-timeout requires --call-phantom');
        process.exit(1);
    }
})
.argv;

if (argv.h || argv.help) return optimist.showHelp();

// Append 'http://' if protocol not specified
var url = argv._[0];
if (!url.match(/^\w+:\/\//)) {
    url = 'http://' + url;
}

var selector = argv.s || argv.selector;
var out_file = argv._[1];

var css_file = argv.c || argv.css;
var css_text = '';
if (css_file) {
    css_text = fs.readFileSync(css_file, 'utf8');
}

var hide_selector = argv.H || argv["hide-selector"];
if (hide_selector) {
    css_text += "\n\n " + hide_selector + " { display: none; }\n";
}

var viewport_width = argv.w || argv['browser-width'];
var delay_time = argv.d || argv['delay'];

var callPhantom = argv['call-phantom'];
var callPhantomTimeout = (argv['call-phantom-timeout'] || 30) * 1000;

var callPhantomTimeoutID;
var hasTakenScreenshot = false;

function depict(url, out_file, selector, css_text) {
    // phantomjs heavily relies on callback functions

    var page;
    var ph;
    var response_code;

    console.log('\nRequesting', url);

    phantom.create({parameters: phantomOptions}, createPage)

    function createPage(_ph) {
        ph = _ph;
        ph.createPage(openPage);
    }

    function openPage(_page) {
        page = _page;
        page.set('onError', function() { return; });
        page.onConsoleMessage = function (msg) { console.log(msg); };

        page.set('onResourceReceived', function(response) {
            // If this resource is the requested URL, save its response code
            if (response.url === url && response.stage === 'end') {
                response_code = response.status;
            }
        });

        if (callPhantom) {
            page.set('onCallback', function(data) {
                // Ensure message sent was for depict
                if (data.target === 'depict') {
                    // Ensure status is ready
                    if (data.status === 'ready') {
                        page.evaluate(runInPhantomBrowser, renderImage, selector, css_text);
                    } else {
                        process.stdout.write('callPhantom() did not have status of `ready`\n');
                        ph.exit();
                        process.exit(1);
                    }
                }
            });
        }
        page.open(url, prepForRender);
        page.set('viewportSize', {width: viewport_width, height: 900}); // The height isn't taken into account here but phantomjs requires an object with both a width and a height.
    }

    function prepForRender(status) {
        if (response_code >= 200 && response_code < 300 && status === 'success') {
            if (callPhantom) {
                callPhantomTimeoutID = setTimeout(function() {
                    if (!hasTakenScreenshot) {
                        process.stdout.write('callPhantom() was not called; depict timed out.\n');
                        ph.exit();
                        process.exit(1);
                    }
                }, callPhantomTimeout);
            } else {
                page.evaluate(runInPhantomBrowser, renderImage, selector, css_text);
            }
        } else {
            ph.exit()
            process.stdout.write('Page could not be loaded. Response code: ' + response_code + '\n');
            process.exit(1);
        }
    }

    function runInPhantomBrowser(selector, css_text) {
        if (css_text) {
            var style = document.createElement('style');
            style.appendChild(document.createTextNode(css_text));
            document.head.appendChild(style);
        }

        var element = document.querySelector(selector);
        return element.getBoundingClientRect();
    }

    function renderImage(rect) {
        // Clear the phantom timeout
        clearTimeout(callPhantomTimeoutID);

        // Not technically true yet, but at this point the screenshot will clearly
        // be taken.
        hasTakenScreenshot = true;
        setTimeout(function(){
            page.set('clipRect', rect);
            page.render(out_file, cleanup);
        }, delay_time)
    }

    function cleanup() {
        console.log('Saved imaged to', out_file);
        ph.exit();
    }
}

depict(url, out_file, selector, css_text);
<commit_msg>Add `ignore-ssl-errors` option<commit_after>#!/usr/bin/env node

var child_process = require('child_process');
var fs = require('fs');
var optimist = require('optimist');
var phantom = require('phantom');
// http://phantomjs.org/api/command-line.html
var phantomOptions = {
    'web-security': 'false',
    'ignore-ssl-errors': 'true'
};

var argv = optimist
.usage('Usage: depict URL OUT_FILE [OPTIONS]')
.options('h', {
    alias: 'help',
    describe: 'Display help',
    default: false
})
.options('s', {
    alias: 'selector',
    describe: 'CSS selector',
    default: 'body'
})
.options('c', {
    alias: 'css',
    describe: 'CSS file to include in rendering',
    default: false
})
.options('H', {
    alias: 'hide-selector',
    describe: 'Hide attributes of this selector before rendering.',
    default: false
})
.options('w', {
    alias: 'browser-width',
    describe: 'Specify the desired browser width.',
    default: 1440
})
.options('d', {
    alias: 'delay',
    describe: 'Specify a delay time, in milliseconds.',
    default: 1000
})
.options('call-phantom', {
    describe: 'Whether to wait for the target page to call `callPhantom()`.',
    default: false
})
.options('call-phantom-timeout', {
    describe: 'How long to wait for the target page to call `callPhantom()`, in seconds.',
    default: 30
})
.check(function(argv) {
    if (argv._.length !== 2) {
        throw new Error('URL and OUT_FILE must be given.');
        process.exit(1);
    }
    if (argv['call-phantom-timeout'] !== 30 && !argv['call-phantom']) {
        throw new Error('--call-phantom-timeout requires --call-phantom');
        process.exit(1);
    }
})
.argv;

if (argv.h || argv.help) return optimist.showHelp();

// Append 'http://' if protocol not specified
var url = argv._[0];
if (!url.match(/^\w+:\/\//)) {
    url = 'http://' + url;
}

var selector = argv.s || argv.selector;
var out_file = argv._[1];

var css_file = argv.c || argv.css;
var css_text = '';
if (css_file) {
    css_text = fs.readFileSync(css_file, 'utf8');
}

var hide_selector = argv.H || argv["hide-selector"];
if (hide_selector) {
    css_text += "\n\n " + hide_selector + " { display: none; }\n";
}

var viewport_width = argv.w || argv['browser-width'];
var delay_time = argv.d || argv['delay'];

var callPhantom = argv['call-phantom'];
var callPhantomTimeout = (argv['call-phantom-timeout'] || 30) * 1000;

var callPhantomTimeoutID;
var hasTakenScreenshot = false;

function depict(url, out_file, selector, css_text) {
    // phantomjs heavily relies on callback functions

    var page;
    var ph;
    var response_code;

    console.log('\nRequesting', url);

    phantom.create({parameters: phantomOptions}, createPage)

    function createPage(_ph) {
        ph = _ph;
        ph.createPage(openPage);
    }

    function openPage(_page) {
        page = _page;
        page.set('onError', function() { return; });
        page.onConsoleMessage = function (msg) { console.log(msg); };

        page.set('onResourceReceived', function(response) {
            // If this resource is the requested URL, save its response code
            if (response.url === url && response.stage === 'end') {
                response_code = response.status;
            }
        });

        if (callPhantom) {
            page.set('onCallback', function(data) {
                // Ensure message sent was for depict
                if (data.target === 'depict') {
                    // Ensure status is ready
                    if (data.status === 'ready') {
                        page.evaluate(runInPhantomBrowser, renderImage, selector, css_text);
                    } else {
                        process.stdout.write('callPhantom() did not have status of `ready`\n');
                        ph.exit();
                        process.exit(1);
                    }
                }
            });
        }
        page.open(url, prepForRender);
        page.set('viewportSize', {width: viewport_width, height: 900}); // The height isn't taken into account here but phantomjs requires an object with both a width and a height.
    }

    function prepForRender(status) {
        if (response_code >= 200 && response_code < 300 && status === 'success') {
            if (callPhantom) {
                callPhantomTimeoutID = setTimeout(function() {
                    if (!hasTakenScreenshot) {
                        process.stdout.write('callPhantom() was not called; depict timed out.\n');
                        ph.exit();
                        process.exit(1);
                    }
                }, callPhantomTimeout);
            } else {
                page.evaluate(runInPhantomBrowser, renderImage, selector, css_text);
            }
        } else {
            ph.exit()
            process.stdout.write('Page could not be loaded. Response code: ' + response_code + '\n');
            process.exit(1);
        }
    }

    function runInPhantomBrowser(selector, css_text) {
        if (css_text) {
            var style = document.createElement('style');
            style.appendChild(document.createTextNode(css_text));
            document.head.appendChild(style);
        }

        var element = document.querySelector(selector);
        return element.getBoundingClientRect();
    }

    function renderImage(rect) {
        // Clear the phantom timeout
        clearTimeout(callPhantomTimeoutID);

        // Not technically true yet, but at this point the screenshot will clearly
        // be taken.
        hasTakenScreenshot = true;
        setTimeout(function(){
            page.set('clipRect', rect);
            page.render(out_file, cleanup);
        }, delay_time)
    }

    function cleanup() {
        console.log('Saved imaged to', out_file);
        ph.exit();
    }
}

depict(url, out_file, selector, css_text);
<|endoftext|>


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

/* BEGIN NPM-GLUE */

// This code block will be striped when building the stand-alone version.
// When using `npm` this exports the correct functions in order to be easily
// imported in the correct scope, without leaking to the global scope.

var window = typeof window !== "undefined" && window || {};
module.exports = window;

/* END NPM-GLUE */

// ((global) => {

  /**
   * Create a VNode element
   *
   * @param {String|Function} element - The tag name or the component to render
   * @param {Object} [props] - The object properties
   * @param {Array} [children] - The child VNode elements
   * @returns {VNode} Returns a virtual DOM instance
   */
  let c = (element, props={}, ...children) => (
    element = ({                                                      // The reference of `element` will be kept
                                                                      // in the object so we are safe to replace it

      $: element,                                                     // 'E' holds the name or function passed as
                                                                      // first argument

      a: (props.$ || props.trim || props.map)                         // If the props argument is a renderable VNode,
                                                                      // a string or an array, then

          ? {c: [].concat(props, ...children)}                        // ... prepend it to the children
          : (props.c = [].concat(...children)) && props               // ... otherwise append 'C' to the property
                                                                      // the .concat ensures that arrays of children
                                                                      // will be flattened into a single array.

      }
    ),

    // element.a.className = element.a.className || '',               // Ensure `className` always exist, since this
                                                                      // way we can cleanly replace className when
                                                                      // removed.
    element
  )

  , z = (a = [], b, c) => a.map(e => e(b, c))                         // z is a helper method that calls all methods in
                                                                      // an array of functions (used for life cycle hooks)

  , o = Object.assign                                                 // o is just an Object.assign short-hand helper
                                                                      // which helps save some bytes in the end

  /**
   * Render a VNode in the DOM
   *
   * @param {VNode|Array<VNode>} vnodes - The node on an array of nodes to render
   * @param {HTLDomElement}
   */
  let r = window.R = (
    vnodes,                                                           // 1. The vnode tree to render
    dom,                                                              // 2. The DOMElement where to render into

    _npath,                                                           // a. The current state path
    _children=dom.childNodes,                                         // b. Shorthand for accessing the children
    _c=0                                                              // c. Counter for processed children
  ) => {

    (vnodes.map ? vnodes : [vnodes]).map(                             // Cast `vnodes` to array if nor already

                                                                      // In this `map` loop we ensure that the DOM
                                                                      // elements correspond to the correct virtual
                                                                      // node elements.
      (
        vnode,                                                        // 1. We handle the vnode from the array
        index,                                                        // 2. And the index

        _unused1,                                                     // We don't handle the array, but we need the
                                                                      // placeholder for the local variables after

        _path=_npath+' '+index,                                       // a. The state path of this vnode
        _path_state=w[_path] || [{}, vnode.$],                        // b. Get the state record for this path
        _state=(                                                      // c. Update and get the state record
          w[_path] =                                                  //    The record is an the following format:
            _path_state[1] != vnode.$                                 //  [ {state object},
            ? [{}, vnode.$]                                           //    'vnode element' ]
            : _path_state                                             //    The second component is needed in order to
        ),                                                            //    reset the state if the component has changed
        _child=_children[_c++],                                       // d. Get the next DOM child + increment counter
        _hooks={a:vnode.$,m:[],u:[],d:[]},
        _new_dom                                                      // e. The new DOM element placeholder

      ) => {

        /* Expand functional Components */

        (vnode.$ || _unused1).call &&                                 // (This expands to : vnode.$ && vnode.$.call &&)
                                                                      // If the vnode is a functional component, expand
          (vnode = vnode.$(                                           // it and replace the current vnode variable.

            vnode.a,                                                  // 1. The component properties
            _state[0],                                                // 2. The stateful component state

            (newState) =>                                             // 3. The setState function

              o(                                                      // First we update the state part of the record
                _state[0],                                            // Note: When we defined the variable we kept the
                newState                                              //       reference to the record array
              ) &&

              r(                                                      // We then trigger the same render cycle that will
                vnodes,                                               // update the DOM
                dom,
                _npath
              ),

              _hooks

          ));

        /* Create new DOM element */

        _new_dom =                                                    // We prepare the new DOM element in advance in
          vnode.trim                                                  // order to spare a few comparison bytes
            ? document.createTextNode(vnode)
            : document.createElement(vnode.$);

        /* Keep or replace the previous DOM element */

        _new_dom =
          _child                                                      // If we have a previous child we first check if
            ? (_child.$ != vnode.$ && _child.data != vnode)           // the VNode element or the text are the same

              ? (
                  dom.replaceChild(                                   // - If not, we replace the old element with the
                    _new_dom,                                         //   new one.
                    _child
                  ),
                  _new_dom                                            //   ... and we make sure we return the new DOM
                )
              : _child                                                // - If it's the same, we keep the old child

            : dom.appendChild(                                        // mount lifecycle method and append
                _new_dom
              );

        /* Call lifecycle methods */

        z(
          _child
            ? _child.a != _hooks.a
              ? (z(_child.u), _hooks.m)
              : _hooks.d
            : _hooks.m
        ,_new_dom, _child);

        /* Update Element */

        o(_new_dom, vnode, _hooks);                                   // Keep the following information in the DOM:
                                                                      // - $ : The tag name from the vnode. We use this
                                                                      //       instead of the .tagName because some
                                                                      //       browsers convert it to capital-case
                                                                      // - u : The `didUnmount` hook that is called when
                                                                      //       the DOM element is removed
                                                                      //
                                                                      // By assigning the entire _hooks and vnode
                                                                      // objects we expose some unneeded properties, but
                                                                      // it occupies less space than assigning $ and u
                                                                      // individually.

        vnode.trim
          ? _new_dom.data = vnode                                     // - String nodes update only the text
          : o(
              _new_dom,
              vnode.a
            )
            &&
            o(
              _new_dom.style,
              vnode.a.style
            )
            &&
            r(                                                        // Only if we have an element (and not  text node)
              vnode.a.c,                                              // we recursively continue rendering into it's
              _new_dom,                                               // child nodes.
              _path
            )
      }
    );

    /* Remove extraneous nodes */

    while (_children[_c]) {                                           // The _c property keeps track of the number of
                                                                      // elements in the VDom. If there are more child
                                                                      // nodes in the DOM, we remove them.

      z(_children[_c].u)                                              // We then call the unmount lifecycle method for the
                                                                      // elements that will be removed

      r(                                                              // Remove child an trigger a recursive child removal
        [],                                                           // in order to call the correct lifecycle methods in our
        dom.removeChild(_children[_c])                                // deep children too.
      )
    }

  }

  /**
   * Helper function that wraps an element shorthand function with a proxy
   * that can be used to append class names to the instance.
   *
   * The result is wrapped with the same function, creating a chainable mechanism
   * for appending classes.
   *
   * @param {function} factoryFn - The factory function to call for creating vnode
   */
  let w = (factoryFn) =>
    new Proxy(                                                        // We are creating a proxy object for every tag in
                                                                      // order to be able to customize the class name
                                                                      // via a shorthand call.
      factoryFn,
      {
        get: (targetFn, className, _instance) =>
          w(
            (...args) => (
              (_instance=targetFn(...args))                           // We first create the Virtual DOM instance by
                                                                      // calling the wrapped factory function

                .a.className = (_instance.a.className || '')          // And then we assign the class name,
                               + ' ' + className,                     // concatenating to the previous value

              _instance                                               // And finally we return the instance
            )
          )
      }
    )

  /**
   * Expose as `H` a proxy around the createElement function that can either be used
   * either as a function (ex. `H('div')`, or as a proxied method `H.div()` for creating
   * virtual DOM elements.
   */
  window.H = new Proxy(
    c,
    {
      get: (targetFn, tagName) =>
        targetFn[tagName] ||                                          // Make sure we don't override any native
                                                                      // property or method from the base function

        w(                                                            // Otherwise, for every tag we extract a
          c.bind(global, tagName)                                     // class-wrapped crateElement method, bound to the
        )                                                             // tag named as the property requested.
    }
  )

// })(window);
<commit_msg>new refactor<commit_after>/**
 * .dom - A Tiny VDom Template Engine
 *
 * Copyright 2017 Ioannis Charalampidis (wavesoft)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* BEGIN NPM-GLUE */

// This code block will be striped when building the stand-alone version.
// When using `npm` this exports the correct functions in order to be easily
// imported in the correct scope, without leaking to the global scope.

var window = typeof window !== "undefined" && window || {};
module.exports = window;

/* END NPM-GLUE */

  /**
   * Create a VNode element
   *
   * @param {String|Function} element - The tag name or the component to render
   * @param {Object} [props] - The object properties
   * @param {Array} [children] - The child VNode elements
   * @returns {VNode} Returns a virtual DOM instance
   */
  let c = (element, props={}, ...children) => (
    element = ({                                                      // The reference of `element` will be kept
                                                                      // in the object so we are safe to replace it

      $: element,                                                     // 'E' holds the name or function passed as
                                                                      // first argument

      a: (props.$ || props.trim || props.map)                         // If the props argument is a renderable VNode,
                                                                      // a string or an array, then

          ? {c: [].concat(props, ...children)}                        // ... prepend it to the children
          : (props.c = [].concat(...children)) && props               // ... otherwise append 'C' to the property
                                                                      // the .concat ensures that arrays of children
                                                                      // will be flattened into a single array.

      }
    ),

    // element.a.className = element.a.className || '',               // Ensure `className` always exist, since this
                                                                      // way we can cleanly replace className when
                                                                      // removed.
    element
  )

  , z = (a = [], b, c) => a.map(e => e(b, c))                         // z is a helper method that calls all methods in
                                                                      // an array of functions (used for life cycle hooks)

  , o = Object.assign                                                 // o is just an Object.assign short-hand helper
                                                                      // which helps save some bytes in the end

  /**
   * Render a VNode in the DOM
   *
   * @param {VNode|Array<VNode>} vnodes - The node on an array of nodes to render
   * @param {HTLDomElement}
   */
  let r = window.R = (
    vnodes,                                                           // 1. The vnode tree to render
    dom,                                                              // 2. The DOMElement where to render into

    _npath,                                                           // a. The current state path
    _children=dom.childNodes,                                         // b. Shorthand for accessing the children
    _c=0                                                              // c. Counter for processed children
  ) => {

    (vnodes.map ? vnodes : [vnodes]).map(                             // Cast `vnodes` to array if nor already

                                                                      // In this `map` loop we ensure that the DOM
                                                                      // elements correspond to the correct virtual
                                                                      // node elements.
      (
        vnode,                                                        // 1. We handle the vnode from the array
        index,                                                        // 2. And the index

        _unused1,                                                     // We don't handle the array, but we need the
                                                                      // placeholder for the local variables after

        _path=_npath+' '+index,                                       // a. The state path of this vnode
        _path_state=w[_path] || [{}, vnode.$],                        // b. Get the state record for this path
        _state=(                                                      // c. Update and get the state record
          w[_path] =                                                  //    The record is an the following format:
            _path_state[1] != vnode.$                                 //  [ {state object},
            ? [{}, vnode.$]                                           //    'vnode element' ]
            : _path_state                                             //    The second component is needed in order to
        ),                                                            //    reset the state if the component has changed
        _child=_children[_c++],                                       // d. Get the next DOM child + increment counter
        _hooks={a:vnode.$,m:[],u:[],d:[]},
        _new_dom                                                      // e. The new DOM element placeholder

      ) => {

        /* Expand functional Components */

        (vnode.$ || _unused1).call &&                                 // (This expands to : vnode.$ && vnode.$.call &&)
                                                                      // If the vnode is a functional component, expand
          (vnode = vnode.$(                                           // it and replace the current vnode variable.

            vnode.a,                                                  // 1. The component properties
            _state[0],                                                // 2. The stateful component state

            (newState) =>                                             // 3. The setState function

              o(                                                      // First we update the state part of the record
                _state[0],                                            // Note: When we defined the variable we kept the
                newState                                              //       reference to the record array
              ) &&

              r(                                                      // We then trigger the same render cycle that will
                vnodes,                                               // update the DOM
                dom,
                _npath
              ),

              _hooks

          ));

        /* Create new DOM element */

        _new_dom =                                                    // We prepare the new DOM element in advance in
          vnode.trim                                                  // order to spare a few comparison bytes
            ? document.createTextNode(vnode)
            : document.createElement(vnode.$);

        /* Keep or replace the previous DOM element */

        _new_dom =
          _child                                                      // If we have a previous child we first check if
            ? (_child.$ != vnode.$ && _child.data != vnode)           // the VNode element or the text are the same

              ? (
                  dom.replaceChild(                                   // - If not, we replace the old element with the
                    _new_dom,                                         //   new one.
                    _child
                  ),
                  _new_dom                                            //   ... and we make sure we return the new DOM
                )
              : _child                                                // - If it's the same, we keep the old child

            : dom.appendChild(                                        // mount lifecycle method and append
                _new_dom
              );

        /* Call lifecycle methods */

        z(
          _child
            ? _child.a != _hooks.a
              ? (z(_child.u), _hooks.m)
              : _hooks.d
            : _hooks.m
        ,_new_dom, _child);

        /* Update Element */

        o(_new_dom, vnode, _hooks);                                   // Keep the following information in the DOM:
                                                                      // - $ : The tag name from the vnode. We use this
                                                                      //       instead of the .tagName because some
                                                                      //       browsers convert it to capital-case
                                                                      // - u : The `didUnmount` hook that is called when
                                                                      //       the DOM element is removed
                                                                      //
                                                                      // By assigning the entire _hooks and vnode
                                                                      // objects we expose some unneeded properties, but
                                                                      // it occupies less space than assigning $ and u
                                                                      // individually.

        vnode.trim
          ? _new_dom.data = vnode                                     // - String nodes update only the text
          : o(
              _new_dom,
              vnode.a
            )
            &&
            o(
              _new_dom.style,
              vnode.a.style
            )
            &&
            r(                                                        // Only if we have an element (and not  text node)
              vnode.a.c,                                              // we recursively continue rendering into it's
              _new_dom,                                               // child nodes.
              _path
            )
      }
    );

    /* Remove extraneous nodes */

    while (_children[_c]) {                                           // The _c property keeps track of the number of
                                                                      // elements in the VDom. If there are more child
                                                                      // nodes in the DOM, we remove them.

      z(_children[_c].u)                                              // We then call the unmount lifecycle method for the
                                                                      // elements that will be removed

      r(                                                              // Remove child an trigger a recursive child removal
        [],                                                           // in order to call the correct lifecycle methods in our
        dom.removeChild(_children[_c])                                // deep children too.
      )
    }

  }

  /**
   * Helper function that wraps an element shorthand function with a proxy
   * that can be used to append class names to the instance.
   *
   * The result is wrapped with the same function, creating a chainable mechanism
   * for appending classes.
   *
   * @param {function} factoryFn - The factory function to call for creating vnode
   */
  let w = (factoryFn) =>
    new Proxy(                                                        // We are creating a proxy object for every tag in
                                                                      // order to be able to customize the class name
                                                                      // via a shorthand call.
      factoryFn,
      {
        get: (targetFn, className, _instance) =>
          w(
            (...args) => (
              (_instance=targetFn(...args))                           // We first create the Virtual DOM instance by
                                                                      // calling the wrapped factory function

                .a.className = (_instance.a.className || '')          // And then we assign the class name,
                               + ' ' + className,                     // concatenating to the previous value

              _instance                                               // And finally we return the instance
            )
          )
      }
    )

  /**
   * Expose as `H` a proxy around the createElement function that can either be used
   * either as a function (ex. `H('div')`, or as a proxied method `H.div()` for creating
   * virtual DOM elements.
   */
  window.H = new Proxy(
    c,
    {
      get: (targetFn, tagName) =>
        targetFn[tagName] ||                                          // Make sure we don't override any native
                                                                      // property or method from the base function

        w(                                                            // Otherwise, for every tag we extract a
          c.bind(global, tagName)                                     // class-wrapped crateElement method, bound to the
        )                                                             // tag named as the property requested.
    }
  )
<|endoftext|>


<commit_before>import CanvasControl from './jsiso/canvas/Control';
import CanvasInput from './jsiso/canvas/Input';
import imgLoader from './jsiso/img/load';
import jsonLoader from './jsiso/json/load';
import TileField from './jsiso/tile/Field';
import pathfind from './jsiso/pathfind/pathfind';

import Player from './player';
import ActionExecutor from './action';

let requestAnimFrame = (function() {
  return window.requestAnimationFrame ||
  window.webkitRequestAnimationFrame  ||
  window.mozRequestAnimationFrame     ||
  window.oRequestAnimationFrame       ||
  window.msRequestAnimationFrame      ||
  function(callback, element) {
    window.setTimeout(callback, 1000 / 60);
  };
})();

const ELEMENT_NAMES = {
  containerName: 'container',
  frameName: 'text-frame',
  messageName: 'text-message',
  indicatorName: 'text-indicator'
};

let appendHtml = (el, str) => {
  let div = document.createElement('div');
  div.innerHTML = str;
  while (div.children.length > 0) {
    el.appendChild(div.children[0]);
  }
}

let createElements = (container, names, outputEnabled=false) => {
  let elements = '

';
  if (outputEnabled) {
    elements += '\

\
        \
        ▼\

';
  }
  appendHtml(container, elements);
}

export default function TileEngine (x, y, xrange, yrange, overrides) {
  let self = this;
  overrides = Object.assign({}, overrides);

  let parent = overrides.parent || document.body;
  let elementNames = Object.assign(ELEMENT_NAMES, overrides.customElementNames);
  if (!overrides.useCustomElements) {
    createElements(parent, elementNames, overrides.enableTextOutput);
  }
  const container = document.getElementById(elementNames.containerName);

  let textMessages = [];
  const textMessageFrame = document.getElementById(elementNames.frameName);
  const textMessage = document.getElementById(elementNames.messageName);
  const textIndicator = document.getElementById(elementNames.indicatorName)

  const controlWidth = container.clientWidth;
  const controlHeight = container.clientHeight;
  const context = CanvasControl.create("canvas", controlWidth, controlHeight, {}, elementNames.containerName, true);

  let backgroundLayers = [];
  let foregroundLayers = [];

  let paused = false;
  let mapLayers = [];
  let players = [];
  let playerMap = {};

  let actionExecutor = new ActionExecutor();
  actionExecutor.registerAction('toggleTile', (options, engine, player) => {
    let layers = mapLayers.filter((layer) => layer.zIndex === player.properties.zIndex);
    layers.forEach((layer) => {
      let currentTile = layer.getTile(options.target.x, options.target.y);
      let currentIndex = options.tiles.indexOf(currentTile);
      layer.setTile(options.target.x, options.target.y, options.tiles[(currentIndex + 1) % options.tiles.length]);
    })
  });

  let clearText = function () {};
  let displayText = function () {};
  let drawMessages = function () {};

  if (overrides.enableTextOutput) {
    textMessageFrame.onclick = () => {
      if (paused) {
        drawMessages();
      }
    };

    clearText = () => {
      textMessages = [];
      textMessageFrame.classList.remove("in");
      textMessage.innerHTML = "";
      textIndicator.classList.remove("in");
      if (paused) {
        unpause();
      }
    }

    displayText = (text) => {
      textMessages = textMessages.concat(text);
    }

    drawMessages = () => {
      if (textMessages.length > 0) {
        pause();
        let text = textMessages.splice(0, 1)[0];
        textMessage.innerHTML = text;
        if (!("in" in textMessageFrame.classList)) {
          textMessageFrame.classList.add("in");
        }
        if (textMessages.length >= 1) {
          textIndicator.classList.add("in");
        } else {
          textIndicator.classList.remove("in");
        }
      } else {
        clearText();
      }
    }
  }

  const input = new CanvasInput(document, CanvasControl());
  if (overrides.enableMouseInput) {
    input.mouse_action(function(coords) {
      if (paused) {
        drawMessages()
      } else {
        let player = getCharacter('player');
        let layer = player.properties.layer;
        let t = layer.applyMouseFocus(coords.x, coords.y);
        player.goTo(t.x, t.y);
        if (Math.abs(t.x - player.getTile().x) + Math.abs(t.y - player.getTile().y) === 1) {
          interact(player, t);
        }
      }
    });
  }

  if (overrides.enableKeyboardInput) {
    input.keyboard(function(pressed, status) {
      let player = getCharacter('player');
      if (status) {
        if (paused) {
          if ([13, 32].indexOf(pressed) >= 0) {
            drawMessages();
          }
        } else {
          switch (pressed) {
            case 37: player.moveTo(player.getTile().x - 1, player.getTile().y); break;
            case 38: player.moveTo(player.getTile().x, player.getTile().y - 1); break;
            case 39: player.moveTo(player.getTile().x + 1, player.getTile().y); break;
            case 40: player.moveTo(player.getTile().x, player.getTile().y + 1); break;
            case 13: case 32: interact(player, player.getLookedAtTile()); break;
          }
        }
      }
    });
  }

  let getActions = (player=null) => {
    let layers = mapLayers;
    if (player !== null) {
      layers = layers.filter((layer) => layer.zIndex === player.properties.zIndex);
    }
    let actions = [];
    layers.forEach((layer) => actions = actions.concat(layer.actions || []));
    return actions;
  }

  let interact = (player, tile) => {
    if (!player.isMoving()) {
      getActions(player)
        .filter((action) => action.type !== actionExecutor.TYPE_POSITIONAL)
        .filter((action) => action.x === tile.x && action.y == tile.y)
        .forEach((action) => actionExecutor.execute(action, self, player));
    }
  };

  let pause = () => {
    paused = true;
  }

  let unpause = () => {
    if (paused) {
      paused = false;
      draw();
    }
  }

  let getCharacter = (id) => playerMap[id];

  let setPlayerLighting = (tile) => {
    mapLayers.forEach((layer) => layer.setLight(tile.x, tile.y));
  }

  let drawLayer = (layer) => {
    for (let i = 0; i < (layer.width || xrange); i++) {
      for (let j = 0; j < (layer.height || yrange); j++) {
        layer.draw(i, j, void 0, (layer.x || x), (layer.y || y));
      }
    }
  }

  let previousTime = 0;
  let timeToDraw = (time) => !overrides.lockedFrameRate || (time - previousTime) >= 1000 / overrides.lockedFrameRate;

  let draw = (time) => {
    if (!timeToDraw(time)) {
      requestAnimationFrame(draw);
    } else {
      previousTime = time;
      context.clearRect(0, 0, controlWidth, controlHeight);
      let thingsToDraw = mapLayers.concat(players).sort((a, b) => a.zIndex > b.zIndex);
      for (let thing of thingsToDraw) {
        if (thing instanceof Player) {
          thing.draw();
          thing.move();
          setPlayerLighting(thing.getTile());
        } else {
          if (thing.visible) {
            drawLayer(thing);
          }
        }
      }
      drawMessages();
      if (!paused) {
        requestAnimationFrame(draw);
      }
    }
  }

  let initLayer = (layer) => {
    let mapLayer = new TileField(context, controlWidth, controlHeight);
    mapLayer.setup(layer);
    mapLayer.flip("horizontal");
    mapLayer.rotate("left");
    mapLayer.setLightmap(layer.lightmap);
    mapLayer = Object.assign(mapLayer, layer);
    return mapLayer;
  }

  let computeOnce = (fn) => {
    var instance = void 0;
    return function () {
      if (instance === void 0) {
        instance = fn.apply(this, arguments);
      }
      return instance;
    }
  }

  let createEmptyLayer = computeOnce((map) => {
    let layer = {
      width: map.width,
      height: map.height,
      layout: Array(map.width * map.height).fill(0)
    }
    return initLayer(layer);
  });

  let init = (map) => {
    mapLayers = map.layers.map(initLayer);
    draw();
    let characters = map.characters || {};
    for (let characterId of Object.keys(characters)) {
      let playerOptions = characters[characterId];
      if (playerOptions) {
        imgLoader([{
          graphics: [playerOptions.sprites],
          spritesheet: {
            width: playerOptions.width,
            height: playerOptions.height
          }
        }]).then((playerImages) => {
          playerOptions.files = playerImages[0].files;
          playerOptions.layer = mapLayers[0];
          playerOptions.pathfindingLayer = mapLayers[playerOptions.pathfindingLayer] || createEmptyLayer(map);
          playerOptions.tileWidth = map.tileWidth;
          playerOptions.tileHeight = map.tileHeight;

          let player = new Player(context, playerOptions, playerOptions.x, playerOptions.y, pathfind);
          player.on("changeTile", (p, tile) => {
            getActions().filter((action) => action.type === actionExecutor.TYPE_POSITIONAL).forEach((action) => {
              if (action.x === tile.x && action.y === tile.y) {
                actionExecutor.execute(action, self, p);
              }
            })
          });
          players.push(player);
          playerMap[characterId] = player;
        });
      }
    }
  }

  this.init = init;
  this.displayText = displayText;
  this.pause = pause;
  this.unpause = unpause;
  this.getCharacter = getCharacter;

  this.actionExecutor = actionExecutor;
}
<commit_msg>Promisify engine initialization<commit_after>import CanvasControl from './jsiso/canvas/Control';
import CanvasInput from './jsiso/canvas/Input';
import imgLoader from './jsiso/img/load';
import jsonLoader from './jsiso/json/load';
import TileField from './jsiso/tile/Field';
import pathfind from './jsiso/pathfind/pathfind';

import Player from './player';
import ActionExecutor from './action';

let requestAnimFrame = (function() {
  return window.requestAnimationFrame ||
  window.webkitRequestAnimationFrame  ||
  window.mozRequestAnimationFrame     ||
  window.oRequestAnimationFrame       ||
  window.msRequestAnimationFrame      ||
  function(callback, element) {
    window.setTimeout(callback, 1000 / 60);
  };
})();

const ELEMENT_NAMES = {
  containerName: 'container',
  frameName: 'text-frame',
  messageName: 'text-message',
  indicatorName: 'text-indicator'
};

let appendHtml = (el, str) => {
  let div = document.createElement('div');
  div.innerHTML = str;
  while (div.children.length > 0) {
    el.appendChild(div.children[0]);
  }
}

let createElements = (container, names, outputEnabled=false) => {
  let elements = '

';
  if (outputEnabled) {
    elements += '\

\
        \
        ▼\

';
  }
  appendHtml(container, elements);
}

export default function TileEngine (x, y, xrange, yrange, overrides) {
  let self = this;
  overrides = Object.assign({}, overrides);

  let parent = overrides.parent || document.body;
  let elementNames = Object.assign(ELEMENT_NAMES, overrides.customElementNames);
  if (!overrides.useCustomElements) {
    createElements(parent, elementNames, overrides.enableTextOutput);
  }
  const container = document.getElementById(elementNames.containerName);

  let textMessages = [];
  const textMessageFrame = document.getElementById(elementNames.frameName);
  const textMessage = document.getElementById(elementNames.messageName);
  const textIndicator = document.getElementById(elementNames.indicatorName)

  const controlWidth = container.clientWidth;
  const controlHeight = container.clientHeight;
  const context = CanvasControl.create("canvas", controlWidth, controlHeight, {}, elementNames.containerName, true);

  let backgroundLayers = [];
  let foregroundLayers = [];

  let paused = false;
  let mapLayers = [];
  let players = [];
  let playerMap = {};

  let actionExecutor = new ActionExecutor();
  actionExecutor.registerAction('toggleTile', (options, engine, player) => {
    let layers = mapLayers.filter((layer) => layer.zIndex === player.properties.zIndex);
    layers.forEach((layer) => {
      let currentTile = layer.getTile(options.target.x, options.target.y);
      let currentIndex = options.tiles.indexOf(currentTile);
      layer.setTile(options.target.x, options.target.y, options.tiles[(currentIndex + 1) % options.tiles.length]);
    })
  });

  let clearText = function () {};
  let displayText = function () {};
  let drawMessages = function () {};

  if (overrides.enableTextOutput) {
    textMessageFrame.onclick = () => {
      if (paused) {
        drawMessages();
      }
    };

    clearText = () => {
      textMessages = [];
      textMessageFrame.classList.remove("in");
      textMessage.innerHTML = "";
      textIndicator.classList.remove("in");
      if (paused) {
        unpause();
      }
    }

    displayText = (text) => {
      textMessages = textMessages.concat(text);
    }

    drawMessages = () => {
      if (textMessages.length > 0) {
        pause();
        let text = textMessages.splice(0, 1)[0];
        textMessage.innerHTML = text;
        if (!("in" in textMessageFrame.classList)) {
          textMessageFrame.classList.add("in");
        }
        if (textMessages.length >= 1) {
          textIndicator.classList.add("in");
        } else {
          textIndicator.classList.remove("in");
        }
      } else {
        clearText();
      }
    }
  }

  const input = new CanvasInput(document, CanvasControl());
  if (overrides.enableMouseInput) {
    input.mouse_action(function(coords) {
      if (paused) {
        drawMessages()
      } else {
        let player = getCharacter('player');
        let layer = player.properties.layer;
        let t = layer.applyMouseFocus(coords.x, coords.y);
        player.goTo(t.x, t.y);
        if (Math.abs(t.x - player.getTile().x) + Math.abs(t.y - player.getTile().y) === 1) {
          interact(player, t);
        }
      }
    });
  }

  if (overrides.enableKeyboardInput) {
    input.keyboard(function(pressed, status) {
      let player = getCharacter('player');
      if (status) {
        if (paused) {
          if ([13, 32].indexOf(pressed) >= 0) {
            drawMessages();
          }
        } else {
          switch (pressed) {
            case 37: player.moveTo(player.getTile().x - 1, player.getTile().y); break;
            case 38: player.moveTo(player.getTile().x, player.getTile().y - 1); break;
            case 39: player.moveTo(player.getTile().x + 1, player.getTile().y); break;
            case 40: player.moveTo(player.getTile().x, player.getTile().y + 1); break;
            case 13: case 32: interact(player, player.getLookedAtTile()); break;
          }
        }
      }
    });
  }

  let getActions = (player=null) => {
    let layers = mapLayers;
    if (player !== null) {
      layers = layers.filter((layer) => layer.zIndex === player.properties.zIndex);
    }
    let actions = [];
    layers.forEach((layer) => actions = actions.concat(layer.actions || []));
    return actions;
  }

  let interact = (player, tile) => {
    if (!player.isMoving()) {
      getActions(player)
        .filter((action) => action.type !== actionExecutor.TYPE_POSITIONAL)
        .filter((action) => action.x === tile.x && action.y == tile.y)
        .forEach((action) => actionExecutor.execute(action, self, player));
    }
  };

  let pause = () => {
    paused = true;
  }

  let unpause = () => {
    if (paused) {
      paused = false;
      draw();
    }
  }

  let getCharacter = (id) => playerMap[id];

  let setPlayerLighting = (tile) => {
    mapLayers.forEach((layer) => layer.setLight(tile.x, tile.y));
  }

  let drawLayer = (layer) => {
    for (let i = 0; i < (layer.width || xrange); i++) {
      for (let j = 0; j < (layer.height || yrange); j++) {
        layer.draw(i, j, void 0, (layer.x || x), (layer.y || y));
      }
    }
  }

  let previousTime = 0;
  let timeToDraw = (time) => !overrides.lockedFrameRate || (time - previousTime) >= 1000 / overrides.lockedFrameRate;

  let draw = (time) => {
    if (!timeToDraw(time)) {
      requestAnimationFrame(draw);
    } else {
      previousTime = time;
      context.clearRect(0, 0, controlWidth, controlHeight);
      let thingsToDraw = mapLayers.concat(players).sort((a, b) => a.zIndex > b.zIndex);
      for (let thing of thingsToDraw) {
        if (thing instanceof Player) {
          thing.draw();
          thing.move();
          setPlayerLighting(thing.getTile());
        } else {
          if (thing.visible) {
            drawLayer(thing);
          }
        }
      }
      drawMessages();
      if (!paused) {
        requestAnimationFrame(draw);
      }
    }
  }

  let initLayer = (layer) => {
    let mapLayer = new TileField(context, controlWidth, controlHeight);
    mapLayer.setup(layer);
    mapLayer.flip("horizontal");
    mapLayer.rotate("left");
    mapLayer.setLightmap(layer.lightmap);
    mapLayer = Object.assign(mapLayer, layer);
    return mapLayer;
  }

  let computeOnce = (fn) => {
    var instance = void 0;
    return function () {
      if (instance === void 0) {
        instance = fn.apply(this, arguments);
      }
      return instance;
    }
  }

  let createEmptyLayer = computeOnce((map) => {
    let layer = {
      width: map.width,
      height: map.height,
      layout: Array(map.width * map.height).fill(0)
    }
    return initLayer(layer);
  });

  let init = (map) => {
    mapLayers = map.layers.map(initLayer);
    draw();
    let promises = [];
    let characters = map.characters || {};
    for (let characterId of Object.keys(characters)) {
      let playerOptions = characters[characterId];
      if (playerOptions) {
        promises.push(imgLoader([{
          graphics: [playerOptions.sprites],
          spritesheet: {
            width: playerOptions.width,
            height: playerOptions.height
          }
        }]).then((playerImages) => {
          playerOptions.files = playerImages[0].files;
          playerOptions.layer = mapLayers[0];
          playerOptions.pathfindingLayer = mapLayers[playerOptions.pathfindingLayer] || createEmptyLayer(map);
          playerOptions.tileWidth = map.tileWidth;
          playerOptions.tileHeight = map.tileHeight;

          let player = new Player(context, playerOptions, playerOptions.x, playerOptions.y, pathfind);
          player.on("changeTile", (p, tile) => {
            getActions().filter((action) => action.type === actionExecutor.TYPE_POSITIONAL).forEach((action) => {
              if (action.x === tile.x && action.y === tile.y) {
                actionExecutor.execute(action, self, p);
              }
            })
          });
          players.push(player);
          playerMap[characterId] = player;
        }));
      }
    }
    return Promise.all(promises);
  }

  this.init = init;
  this.displayText = displayText;
  this.pause = pause;
  this.unpause = unpause;
  this.getCharacter = getCharacter;

  this.actionExecutor = actionExecutor;
}
<|endoftext|>


<commit_before>(function() {

/**
 * @class Echo.Events
 * Library for exchanging messages between components on the page. It also provides external interface for users to subscribe to a certain events (like "app was rendered", "user logged in", etc).
 *
 * Contexts used in this library are complex identifiers constructed using the following rules:
 *     <contextId> :: "<id>" or "<parentContextID>/<id>", where
 *     <id> :: some unique identifier assigned to component
 *     <parentContextID> :: "<contextID>"
 */
Echo.Events = {};

/**
 * Function allowing to subscribe to an event with a specific callback function and topic.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 * Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {Boolean} [params.once=false]
 * Specifies that provided handler should be executed exactly once (handler will be unsubscribed right before its execution).
 *
 * @param {Function} params.handler
 * Callback function which will be called when event is published
 *
 * @param {String} params.handler.topic
 * Event name (same as params.topic).
 *
 * @param {Object} params.handler.data
 * Arbitrary data object passed to the {@link #publish} function.
 *
 * @return {String}
 * Unique identifier for the current subscription which can be used for unsubscribing.
 */
Echo.Events.subscribe = function(params) {
	var handlerId = Echo.Utils.getUniqueString();
	var context = _initContext(params.topic, params.context);
	if (params.once) {
		var handler = params.handler;
		params.handler = function() {
			Echo.Events.unsubscribe({"handlerId": handlerId});
			handler.apply(this, arguments);
		};
	}
	_executeForDeepestContext(params.topic, context, function(obj, lastContext) {
		obj[lastContext].handlers.push({
			"id": handlerId,
			"handler": params.handler
		});
	});
	_dataByHandlerId[handlerId] = {
		"context": context,
		"topic": params.topic
	};
	return handlerId;
};

/**
 * Function allowing to unsubscribe from an event.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 * Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {String} params.handlerId
 * Unique identifier from the {@link #subscribe} function.
 *
 * @return {Boolean}
 * Unsubscription status.
 */
Echo.Events.unsubscribe = function(params) {
	var unsubscribed = false;
	if (params.handlerId) {
		if (_dataByHandlerId[params.handlerId]) {
			params.topic = _dataByHandlerId[params.handlerId].topic;
			params.context = _dataByHandlerId[params.handlerId].context;
		} else {
			// trying to unsubsribe from previously unsubscribed handler
			return false;
		}
	} else {
		params.context = _initContext(params.topic, params.context);
	}
	if (params.handlerId || params.topic) {
		var obj = _executeForDeepestContext(params.topic, params.context, function(obj, lastContext) {
			if (params.handlerId) {
				$.each(obj[lastContext].handlers, function(i, data) {
					if (data.id === params.handlerId) {
						obj[lastContext].handlers.splice(i, 1);
						delete _dataByHandlerId[data.id];
						unsubscribed = true;
						return false;
					}
				});
			} else {
				$.each(obj[lastContext].handlers, function(i, data) {
					delete _dataByHandlerId[data.id];
				});
				delete obj[lastContext];
				unsubscribed = true;
			}
		});
	} else {
		$.each(Echo.Events._subscriptions, function(topic, data) {
			_executeForDeepestContext(topic, params.context, function(obj, lastContext) {
				$.each(obj[lastContext].handlers, function(i, data) {
					delete _dataByHandlerId[data.id];
				});
				delete obj[lastContext];
				unsubscribed = true;
			});
		});
	}
	return unsubscribed;
};

/**
 * Function allowing to publish an event providing arbitrary data.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 *  Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {String} [params.data]
 * Some data object.
 *
 * @param {Boolean} [params.bubble=true]
 * Indicates whether a given event should be propagated into the parent contexts.
 *
 * @param {Boolean} [params.propagation=true]
 * Indicates whether a given event should be propagated into the child contexts AND executed for the current context.
 *
 * @param {Boolean} [params.global=true]
 * Specifies whether the event should be also published to "global" context or not
 */
Echo.Events.publish = function(params) {
	params = $.extend({
		"bubble": true,
		"propagation": true,
		"global": true
	}, params);
	delete _lastHandlerResult[params.topic];
	params.context = _initContext(params.topic, params.context);
	_executeForDeepestContext(params.topic, params.context, function(obj, lastContext, restContexts) {
		_callHandlers(obj[lastContext], params, restContexts);
	});
	if (params.global && params.context !== "global") {
		params.context = "global";
		Echo.Events.publish(params);
	}
};

var _lastHandlerResult = {}, _dataByHandlerId = {};
var _subscriptions = Echo.Events._subscriptions = {};

var _initContext = function(topic, context) {
	context = context || "global";
	if (topic) {
		var obj = Echo.Events._subscriptions[topic] = Echo.Events._subscriptions[topic] || {};
		$.each(context.split("/"), function(i, part) {
			if (!obj[part]) {
				obj[part] = {
					"contexts": {},
					"handlers": []
				};
			}
			obj = obj[part].contexts;
		});
	}
	return context;
};

var _executeForDeepestContext = function(topic, context, callback) {
	var parts = context.split("/");
	var lastContext = parts.pop();
	var obj = Echo.Events._subscriptions[topic];
	$.each(parts, function(i, part) {
		obj = obj[part].contexts;
	});
	if (obj[lastContext]) {
		callback(obj, lastContext, parts);
	}
};

var _shouldStopEvent = function(stopperType, topic) {
	var stoppers = _lastHandlerResult[topic] && _lastHandlerResult[topic].stop;
	if (!stoppers) {
		return false;
	}
	return stopperType === "bubble"
		? ~$.inArray("bubble", stoppers)
		: ~$.inArray("propagation", stoppers) || ~$.inArray(stopperType, stoppers);
};

var _callHandlers = function(obj, params, restContexts) {
	// use copy of handler list so that inner unsubscribe actions couldn't mess it up
	var handlers = (obj.handlers || []).slice(0);
	$.each(handlers, function(i, data) {
		_lastHandlerResult[params.topic] = data.handler(params.topic, params.data);
		if (_shouldStopEvent("propagation.siblings", params.topic)) {
			return false;
		}
	});
	if (params.bubble && !_shouldStopEvent("bubble", params.topic) && restContexts.length) {
		// copy incoming parameters object so that we can manipulate it freely
		var _params = $.extend({}, params);
		_params.context = restContexts.join("/");
		_params.global = false;
		_params.propagation = false;
		Echo.Events.publish(_params);
	}
	if (params.propagation && !_shouldStopEvent("propagation.children", params.topic)) {
		// copy incoming parameters object so that we can manipulate it freely
		var _params = $.extend({}, params);
		_params.global = false;
		_params.bubble = false;
		$.each(obj.contexts, function(id, context) {
			_callHandlers(context, _params, []);
			if (_shouldStopEvent("propagation.siblings", _params.topic)) {
				return false;
			}
		});
	}
};

})();
<commit_msg>[CR] Echo.Events<commit_after>(function() {

/**
 * @class Echo.Events
 * Library for exchanging messages between components on the page. It also provides external interface for users to subscribe to a certain events (like "app was rendered", "user logged in", etc).
 *
 * Contexts used in this library are complex identifiers constructed using the following rules:
 *     <contextId> :: "<id>" or "<parentContextID>/<id>", where
 *     <id> :: some unique identifier assigned to component
 *     <parentContextID> :: "<contextID>"
 */
Echo.Events = {};

/**
 * Function allowing to subscribe to an event with a specific callback function and topic.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 * Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {Boolean} [params.once=false]
 * Specifies that provided handler should be executed exactly once (handler will be unsubscribed right before its execution).
 *
 * @param {Function} params.handler
 * Callback function which will be called when event is published
 *
 * @param {String} params.handler.topic
 * Event name (same as params.topic).
 *
 * @param {Object} params.handler.data
 * Arbitrary data object passed to the {@link #publish} function.
 *
 * @return {String}
 * Unique identifier for the current subscription which can be used for unsubscribing.
 */
Echo.Events.subscribe = function(params) {
	var handlerId = Echo.Utils.getUniqueString();
	var context = _initContext(params.topic, params.context);
	if (params.once) {
		var handler = params.handler;
		params.handler = function() {
			Echo.Events.unsubscribe({"handlerId": handlerId});
			handler.apply(this, arguments);
		};
	}
	_executeForDeepestContext(params.topic, context, function(obj, lastContext) {
		obj[lastContext].handlers.push({
			"id": handlerId,
			"handler": params.handler
		});
	});
	_dataByHandlerId[handlerId] = {
		"context": context,
		"topic": params.topic
	};
	return handlerId;
};

/**
 * Function allowing to unsubscribe from an event.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 * Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {String} params.handlerId
 * Unique identifier from the {@link #subscribe} function.
 *
 * @return {Boolean}
 * Unsubscription status.
 */
Echo.Events.unsubscribe = function(params) {
	var unsubscribed = false;
	if (params.handlerId) {
		if (_dataByHandlerId[params.handlerId]) {
			params.topic = _dataByHandlerId[params.handlerId].topic;
			params.context = _dataByHandlerId[params.handlerId].context;
		} else {
			// trying to unsubsribe from previously unsubscribed handler
			return false;
		}
	} else {
		params.context = _initContext(params.topic, params.context);
	}
	if (params.handlerId || params.topic) {
		var obj = _executeForDeepestContext(params.topic, params.context, function(obj, lastContext) {
			if (params.handlerId) {
				$.each(obj[lastContext].handlers, function(i, data) {
					if (data.id === params.handlerId) {
						obj[lastContext].handlers.splice(i, 1);
						delete _dataByHandlerId[data.id];
						unsubscribed = true;
						return false;
					}
				});
			} else {
				$.each(obj[lastContext].handlers, function(i, data) {
					delete _dataByHandlerId[data.id];
				});
				delete obj[lastContext];
				unsubscribed = true;
			}
		});
	} else {
		$.each(Echo.Events._subscriptions, function(topic, data) {
			_executeForDeepestContext(topic, params.context, function(obj, lastContext) {
				$.each(obj[lastContext].handlers, function(i, data) {
					delete _dataByHandlerId[data.id];
				});
				delete obj[lastContext];
				unsubscribed = true;
			});
		});
	}
	return unsubscribed;
};

/**
 * Function allowing to publish an event providing arbitrary data.
 *
 * @static
 * @param {Object} params
 * Configuration parameters object with the following fields:
 *
 * @param {String} params.topic
 *  Event name.
 *
 * @param {String} [params.context]
 * Unique identifier for inter-component communication.
 *
 * @param {String} [params.data]
 * Some data object.
 *
 * @param {Boolean} [params.bubble=true]
 * Indicates whether a given event should be propagated into the parent contexts.
 *
 * @param {Boolean} [params.propagation=true]
 * Indicates whether a given event should be propagated into the child contexts AND executed for the current context.
 *
 * @param {Boolean} [params.global=true]
 * Specifies whether the event should be also published to "global" context or not
 */
Echo.Events.publish = function(params) {
	params = $.extend({
		"bubble": true,
		"propagation": true,
		"global": true
	}, params);
	delete _lastHandlerResult[params.topic];
	params.context = _initContext(params.topic, params.context);
	_executeForDeepestContext(params.topic, params.context, function(obj, lastContext, restContexts) {
		_callHandlers(obj[lastContext], params, restContexts);
	});
	if (params.global && params.context !== "global") {
		params.context = "global";
		Echo.Events.publish(params);
	}
};

var _lastHandlerResult = {}, _dataByHandlerId = {};
Echo.Events._subscriptions = {};

var _initContext = function(topic, context) {
	context = context || "global";
	if (topic) {
		var obj = Echo.Events._subscriptions[topic] = Echo.Events._subscriptions[topic] || {};
		$.each(context.split("/"), function(i, part) {
			if (!obj[part]) {
				obj[part] = {
					"contexts": {},
					"handlers": []
				};
			}
			obj = obj[part].contexts;
		});
	}
	return context;
};

var _executeForDeepestContext = function(topic, context, callback) {
	var parts = context.split("/");
	var lastContext = parts.pop();
	var obj = Echo.Events._subscriptions[topic];
	$.each(parts, function(i, part) {
		obj = obj[part].contexts;
	});
	if (obj[lastContext]) {
		callback(obj, lastContext, parts);
	}
};

var _shouldStopEvent = function(stopperType, topic) {
	var stoppers = _lastHandlerResult[topic] && _lastHandlerResult[topic].stop;
	if (!stoppers) {
		return false;
	}
	return stopperType === "bubble"
		? ~$.inArray("bubble", stoppers)
		: ~$.inArray("propagation", stoppers) || ~$.inArray(stopperType, stoppers);
};

var _callHandlers = function(obj, params, restContexts) {
	// use copy of handler list so that inner unsubscribe actions couldn't mess it up
	var _params, handlers = (obj.handlers || []).slice(0);
	$.each(handlers, function(i, data) {
		_lastHandlerResult[params.topic] = data.handler(params.topic, params.data);
		if (_shouldStopEvent("propagation.siblings", params.topic)) {
			return false;
		}
	});
	if (params.bubble && restContexts.length && !_shouldStopEvent("bubble", params.topic)) {
		// copy incoming parameters object so that we can manipulate it freely
		_params = $.extend({}, params);
		_params.context = restContexts.join("/");
		_params.global = false;
		_params.propagation = false;
		Echo.Events.publish(_params);
	}
	if (params.propagation && !_shouldStopEvent("propagation.children", params.topic)) {
		// copy incoming parameters object so that we can manipulate it freely
		_params = $.extend({}, params);
		_params.global = false;
		_params.bubble = false;
		$.each(obj.contexts, function(id, context) {
			_callHandlers(context, _params, []);
			if (_shouldStopEvent("propagation.siblings", _params.topic)) {
				return false;
			}
		});
	}
};

})();
<|endoftext|>


<commit_before>    var eventMatrix = [{
        // Touchable devices
        test: ('propertyIsEnumerable' in window || 'hasOwnProperty' in document) && (window.propertyIsEnumerable('ontouchstart') || document.hasOwnProperty('ontouchstart')),
        events: {
            start: 'touchstart',
            move: 'touchmove',
            end: 'touchend'
        }
    }, {
        // IE10
        test: window.navigator.msPointerEnabled,
        events: {
            start: 'MSPointerDown',
            move: 'MSPointerMove',
            end: 'MSPointerUp'
        }
    }, {
        // Modern device agnostic web
        test: window.navigator.pointerEnabled,
        events: {
            start: 'pointerdown',
            move: 'pointermove',
            end: 'pointerup'
        }
    }];
<commit_msg>Fix safari(ios9) property checking<commit_after>    var eventMatrix = [{
        // Touchable devices
        test: ('propertyIsEnumerable' in window || 'hasOwnProperty' in document) && (window.propertyIsEnumerable('ontouchstart') || document.hasOwnProperty('ontouchstart') || window.hasOwnProperty('ontouchstart')),
        events: {
            start: 'touchstart',
            move: 'touchmove',
            end: 'touchend'
        }
    }, {
        // IE10
        test: window.navigator.msPointerEnabled,
        events: {
            start: 'MSPointerDown',
            move: 'MSPointerMove',
            end: 'MSPointerUp'
        }
    }, {
        // Modern device agnostic web
        test: window.navigator.pointerEnabled,
        events: {
            start: 'pointerdown',
            move: 'pointermove',
            end: 'pointerup'
        }
    }];
<|endoftext|>


<commit_before>var async = require("async");
var Entry = require("entry");
var client = require("client");
var Blog = require("blog");
var build = require("../../build");

var dependentsKey = Entry.key.dependents;

// The purpose of this module is to rebuild any
// entries already in the user's folder which depend
// on the contents of this particular file which was
// just changed or removed.

module.exports = function(blogID, path, callback) {
  Blog.get({ id: blogID }, function(err, blog) {
    client.SMEMBERS(dependentsKey(blogID, path), function(
      err,
      dependent_paths
    ) {
      if (err) return callback(err);

      async.eachSeries(
        dependent_paths,
        function(dependent_path, next) {
          var options = {};

          Metadata.get(blogID, dependent_path, function(err, name) {
            if (err) {
              console.log("Error rebuilding dependents:", path, err);
              return next();
            }

            if (name) options.name = name;

            build(blog, dependent_path, options, function(
              err,
              updated_dependent
            ) {
              if (err) {
                console.log("Error rebuilding dependents:", path, err);
                return next();
              }

              Entry.set(
                blogID,
                dependent_path,
                updated_dependent,
                function(err) {
                  if (err) return callback(err);

                  next();
                },
                false
              );
            });
          });
        },
        callback
      );
    });
  });
};
<commit_msg>Fixed reference to metadata<commit_after>var async = require("async");
var Entry = require("entry");
var client = require("client");
var Blog = require("blog");
var build = require("../../build");
var Metadata = require("metadata");

var dependentsKey = Entry.key.dependents;

// The purpose of this module is to rebuild any
// entries already in the user's folder which depend
// on the contents of this particular file which was
// just changed or removed.

module.exports = function(blogID, path, callback) {
  Blog.get({ id: blogID }, function(err, blog) {
    client.SMEMBERS(dependentsKey(blogID, path), function(
      err,
      dependent_paths
    ) {
      if (err) return callback(err);

      async.eachSeries(
        dependent_paths,
        function(dependent_path, next) {
          var options = {};

          Metadata.get(blogID, dependent_path, function(err, name) {
            if (err) {
              console.log("Error rebuilding dependents:", path, err);
              return next();
            }

            if (name) options.name = name;

            build(blog, dependent_path, options, function(
              err,
              updated_dependent
            ) {
              if (err) {
                console.log("Error rebuilding dependents:", path, err);
                return next();
              }

              Entry.set(
                blogID,
                dependent_path,
                updated_dependent,
                function(err) {
                  if (err) return callback(err);

                  next();
                },
                false
              );
            });
          });
        },
        callback
      );
    });
  });
};
<|endoftext|>