<commit_before>/**
 * @file main
 * @author treelite(c.xinle@gmail.com)
 */

define(function (require) {

    var Emitter = require('saber-emitter');
    var Resolver = require('saber-promise');
    var Tap = require('saber-tap');
    var extend = require('saber-lang/extend');
    var bind = require('saber-lang/bind');
    var curry = require('saber-lang/curry');
    var viewport = require('saber-viewport');
    var mm = require('saber-mm');

    var globalConfig = require('./config');

    var STATUS_IDLE = 0;
    var STATUS_LOAD = 1;
    var filters = [];
    var cachedAction = {};
    var waitingRoute;
    var cur = {};

    cur.status = STATUS_IDLE;

    /**
     * 获取全局配置的附加处理器
     *
     * @inner
     * @param {string} name
     * @return {function|undefined}
     */
    function getProcessor(name) {
        var processor = globalConfig.processor || {};
        return processor[name];
    }

    /**
     * 当前状态设置
     *
     * @inner
     * @param {number} status
     * @param {boolean} force
     */
    function setStatus(status, force) {
        clearTimeout(cur.statusTimer);
        if (status === STATUS_LOAD && !force) {
            // 设置状态回复计时器
            // 在Action加载过久时支持用户切换页面
            cur.statusTimer = setTimeout(
                function () {
                    cur.status = STATUS_IDLE;
                },
                globalConfig.timeout
            );
        }

        cur.status = status;
    }

    /**
     * action加载完成
     *
     * @inner
     */
    function finishLoad() {
        // 设置状态为空闲
        setStatus(STATUS_IDLE);
        // 尝试加载之前被阻塞的action
        tryLoadAction();
    }

    /**
     * 清除所有的缓存action
     *
     * @inner
     */
    function clearCache() {
        var action;
        Object.keys(cachedAction).forEach(function (path) {
            action = cachedAction[path];
            if (cur.action !== action) {
                action.dispose();
            }
        });
        cachedAction = {};
        viewport.delCache();
    }

    /**
     * 删除缓存的action
     *
     * @inner
     * @param {string} path
     */
    function delCache(path) {
        var action = cachedAction[path];
        if (!action) {
            return;
        }
        // 如果不是当前显示的action则进行dispose
        // 如果是当前显示的action，后续leave会处理
        if (cur.action !== action) {
            action.dispose();
        }
        delete cachedAction[path];
        viewport.delCache(path);
    }

    /**
     * 保存当前Action相关信息
     *
     * @inner
     * @param {Action} action
     * @param {Object} route
     * @param {Page} page
     */
    function dumpInfo(action, route, page) {
        if (action) {
            cur.action = action;
            if (route.cached) {
                cachedAction[route.path] = action;
            }
        }
        else {
            cur.action = null;
        }

        cur.route = route;
        cur.page = page;
        cur.path = route.path;
    }

    /**
     * 启动Action
     *
     * @inner
     * @param {Object} config 路由信息
     * @param {string} config.path 请求路径
     * @param {Object} config.action action配置
     * @param {Object} config.query 查询条件
     * @param {boolean=} config.cached 是否缓存action
     * @param {Object=} config.transition 转场配置
     * @param {Object} config.options 跳转参数
     * @param {boolean} config.options.force 强制跳转
     * @param {boolean=} config.optins.noCache 不使用缓存action
     * @param {Object} action
     */
    function enterAction(config, action) {
        var options = config.options || {};

        // 获取页面转场配置参数
        var transition = config.transition || {};
        // 调用全局配置中的处理函数进行转场参数处理
        var processor = getProcessor('transition');
        if (processor) {
            extend(transition, processor(config, cur.route) || {});
        }

        // 如果请求路径没有变化
        // 取消转场效果
        if (config.path === cur.path) {
            transition.type = false;
        }

        var page = viewport.load(
            config.path,
            {
                cached: config.cached,
                noCache: options.noCache
            }
        );

        /**
         * 触发全局事件
         *
         * @inner
         * @type {string} eventName 事件名称
         */
        var fireEvent = (
            function (eventArgBack, eventArgFront) {
                return function (eventName) {
                    exports.emit(eventName, eventArgBack, eventArgFront);
                };
            }
        )(
            {
                route: extend({}, config),
                action: action,
                page: page
            },
            {
                route: extend({}, cur.route),
                action: cur.action,
                page: cur.page
            }
        );

        // 在转场结束时触发afterlaod事件
        page.on('afterenter', curry(fireEvent, 'afterload'));
        // 触发beforeload事件
        fireEvent('beforeload');

        /**
         * 开始转场动画
         *
         * @inner
         * @param {boolean} error
         * @return {Promise}
         */
        function startTransition(error) {
            // 转场开始前 设置强制设置为加载状态
            // 清除状态重置定时器，防止干扰转场动画
            setStatus(STATUS_LOAD, true);
            // 触发`beforetransition`
            fireEvent('beforetransition');

            dumpInfo(!error && action, config, page);

            if (error) {
                return page
                        .enter(transition.type, transition)
                        .then(bind(Resolver.rejected, Resolver));
            }
            return page.enter(transition.type, transition);
        }

        var method;
        var delayMethods = ['complete'];
        var args = [config.path, config.query, config.params, options];

        /**
         * action加载失败处理
         *
         * @inner
         * @return {Promise}
         */
        function enterFail() {
            fireEvent('error');
            return startTransition(true);
        }

        /**
         * 转场正常完成处理
         *
         * @inner
         */
        function finishTransition() {
            delayMethods.forEach(function (name) {
                action[name]();
            });
            finishLoad();
        }

        // 如果没有缓存则使用enter
        // 否则使用wakeup
        if (!cachedAction[config.path]) {
            method = 'enter';
            // 补充enter参数
            args.unshift(page.main);
            // 没有缓存时还需要ready调用
            delayMethods.unshift('ready');
        }
        else {
            method = 'wakeup';
            // 已缓存时需要调用revived
            delayMethods.unshift('revived');
        }

        // 开始加载action
        action[method]
            .apply(action, args)
            // 开始转场操作
            .then(startTransition, enterFail)
            // 转场完成处理
            .then(finishTransition, finishLoad);
    }

    /**
     * 加载Action
     *
     * @inner
     * @param {Object} config 路由信息
     * @param {string} config.path 请求路径
     * @param {Object} config.action action配置
     * @param {Object} config.query 查询条件
     * @param {boolean=} config.cached 是否缓存action
     * @param {Object=} config.transition 转场配置
     * @param {Object} config.options 跳转参数
     * @param {boolean} config.options.force 强制跳转
     * @param {boolean=} config.optins.noCache 不使用缓存action
     */
    function loadAction(config) {
        var options = config.options || {};

        // 如果路径未发生变化
        // 只需要刷新当前action
        if (config.path === cur.path
            && !options.force
            && cur.action // 会有存在cur.path但不存在cur.action的情况，比如action加载失败
            && cur.action.refresh
        ) {
            cur.action.refresh(config.query, config.options).then(finishLoad);
            return;
        }

        if (options.noCache) {
            delCache(config.path);
        }

        // 处理当前正在工作的action
        if (cur.action) {
            cur.action[cachedAction[cur.path] ? 'sleep' : 'leave']();
        }

        // 首先尝试从cache中取action
        var action = cachedAction[config.path];

        // 没有从cache中获取到action就创建
        if (!action) {
            mm.create(config.action).then(curry(enterAction, config));
        }
        else {
            enterAction(config, action);
        }
    }

    /**
     * 执行filter
     *
     * @inner
     * @param {Object} route 路由信息
     * @return {Promise}
     */
    function executeFilter(route) {
        var resolver = new Resolver();
        var index = 0;

        /**
         * 跳过后续的filter
         * 如果不带参数则跳过剩余所有的filter
         *
         * @inner
         * @param {number} num 跳过后续filter的数量
         */
        function jump(num) {
            index += num || filters.length;
            next();
        }

        /**
         * 执行下一个filter
         *
         * @inner
         */
        function next() {
            var item = filters[index++];

            if (!item) {
                resolver.resolve(route);
            }
            else if (!item.url
                || (item.url instanceof RegExp && item.url.test(route.path))
                || item.url === route.path
            ) {
                item.filter(route, next, jump);
            }
            else {
                next();
            }
        }

        next();

        return resolver.promise();
    }

    /**
     * 首屏渲染
     *
     * @inner
     * @param {Page} page
     * @param {Object} action
     */
    function loadFirstScreen(page, action) {
        var route = waitingRoute;
        if (!route) {
            return;
        }

        function fireEvent(eventName) {
            exports.emit(
                eventName,
                {
                    route: null,
                    action: null,
                    page: null
                },
                {
                    route: route,
                    action: action,
                    page: page
                }
            );
        }

        fireEvent('beforeload');

        // 视图与数据已经ready了
        // 跳过enter
        action.view.setMain(page.main);
        // 使用初始化数据填充首屏model
        var initialData = window[globalConfig.initialDataKey];
        if (initialData) {
            action.model.fill(initialData);
        }
        action.model.set(route.query, route.params, route.path)

        fireEvent('beforetransition');

        action.ready(true);
        action.complete();

        fireEvent('afterload');

        dumpInfo(action, route, page);

        waitingRoute = null;

        finishLoad();
    }

    /**
     * 尝试加载Action
     *
     * @inner
     */
    function tryLoadAction() {
        // 如果没有待加载的路由信息
        // 或者当前不是空闲状态
        // 都不再继续加载Action
        if (!waitingRoute || cur.status !== STATUS_IDLE) {
            return;
        }

        // 设置当前状态为正在加载中
        setStatus(STATUS_LOAD);

        var path = waitingRoute.path;

        // 首屏渲染逻辑
        // 第一次加载action且能获取到起始页面
        var page;
        if (globalConfig.firstScreen && !cur.action
            && (page = viewport.front(path, {cached: waitingRoute.cached}))
        ) {
            mm.create(waitingRoute.action).then(curry(loadFirstScreen, page));
            return;
        }

        // 处理filter的执行结果
        function beforeLoad(route) {
            // 如果改变了path则以静默形式重新加载
            if (path !== route.path) {
                // 设置状态为空闲以支持跳转
                setStatus(STATUS_IDLE);
                globalConfig.router.redirect(route.path, route.query, route.options);
            }
            else {
                loadAction(route);
            }
        }

        // 执行filter
        executeFilter(waitingRoute).then(beforeLoad);
        // 进入加载流程了
        // 清除等待加载的route信息
        waitingRoute = null;
    }

    /**
     * 路由导向
     *
     * @inner
     * @param {option} config 路由配置
     * @param {string} path 请求路径
     * @param {Object} query 查询条件
     * @param {Object} params 路径参数
     * @param {string} url 完整的URL
     * @param {Object} options 跳转参数
     */
    function routeTo(config, path, query, params, url, options) {
        // 设置当前的路由信息
        waitingRoute = extend({}, config);
        waitingRoute.path = path;
        waitingRoute.query = query;
        waitingRoute.params = params;
        waitingRoute.options = options;
        waitingRoute.url = url;

        // 尝试加载Action
        tryLoadAction();
    }

    /**
     * 扩展全局配置项
     *
     * @inner
     * @param {Object} options 配置项
     * @return {Object}
     */
    function extendGlobalConfig(options) {
        var config = extend(globalConfig, options);

        if (!Array.isArray(config.template)) {
            config.template = [config.template];
        }

        // 如果没有指定路由器则使用默认的hash路由
        if (!config.router) {
            config.router = require('saber-router');
            config.router.controller(require('saber-router/controller/hash'));
        }

        return config;
    }

    var routes = [];
    var exports = {};

    Emitter.mixin(exports);

    /**
     * 加载path配置信息
     *
     * @public
     * @param {Object} paths
     */
    exports.load = function (paths) {
        if (!Array.isArray(paths)) {
            paths = [paths];
        }

        // 如果还没有制定router
        // 则先缓存路由信息
        // 否则直接添加路由
        if (!globalConfig.router) {
            routes = routes.concat(paths);
        }
        else {
            paths.forEach(function (item) {
                globalConfig.router.add(item.path, curry(routeTo, item));
            });
        }
    };

    /**
     * 启动框架
     *
     * @public
     * @param {HTMLElement} main
     * @param {Object} options 全局配置信息 完整配置参考`./config.js`
     */
    exports.start = function (main, options) {
        // 扩展全局配置信息
        var config = extendGlobalConfig(options);

        var router = config.router;

        mm.config({
            template: config.template,
            templateConfig: config.templateConfig,
            templateData : config.templateData,
            router: router,
            Presenter: config.Presenter,
            View: config.View,
            Model: config.Model
        });

        // 初始化viewport
        viewport.init(main, config.viewport);

        // 启用无延迟点击
        Tap.mixin(document.body);

        // 添加路由
        routes.forEach(function (item) {
            router.add(item.path, curry(routeTo, item));
        });

        router.config({
            path: config.path,
            index: config.index
        });

        // 启动路由
        router.start();
    };

    /**
     * 添加filter
     *
     * @public
     * @param {string|RegExp=} url
     * @param {Function} filter
     */
    exports.addFilter = function (url, filter) {
        if (arguments.length === 1) {
            filter = url;
            url = null;
        }

        filters.push({
            url: url,
            filter: filter
        });
    };

    /**
     * 删除filter
     *
     * @public
     * @param {string|RegExp=} url
     */
    exports.removeFilter = function (url) {
        if (!url) {
            filters = [];
        }
        else {
            var index;
            var res = filters.some(function (item, i) {
                index = i;
                return item.url.toString() === url.toString();
            });
            if (res) {
                filters.splice(index, 1);
            }
        }
    };

    /**
     * 删除缓存的action
     *
     * @public
     * @param {string} path
     */
    exports.delCachedAction = function (path) {
        if (path) {
            delCache(path);
        }
        else {
            clearCache();
        }
    };

    /**
     * 停止App
     * For Test
     *
     * @public
     */
    exports.stop = function () {
        var router = globalConfig.router;

        router.stop();
        router.clear();
        routes = [];

        filters = [];
        waitingRoute = null;
        cur = {status: STATUS_IDLE};

        exports.delCachedAction();
    };

    return exports;

});
<commit_msg>Support synchro `refresh`<commit_after>/**
 * @file main
 * @author treelite(c.xinle@gmail.com)
 */

define(function (require) {

    var Emitter = require('saber-emitter');
    var Resolver = require('saber-promise');
    var Tap = require('saber-tap');
    var extend = require('saber-lang/extend');
    var bind = require('saber-lang/bind');
    var curry = require('saber-lang/curry');
    var viewport = require('saber-viewport');
    var mm = require('saber-mm');

    var globalConfig = require('./config');

    var STATUS_IDLE = 0;
    var STATUS_LOAD = 1;
    var filters = [];
    var cachedAction = {};
    var waitingRoute;
    var cur = {};

    cur.status = STATUS_IDLE;

    /**
     * 获取全局配置的附加处理器
     *
     * @inner
     * @param {string} name
     * @return {function|undefined}
     */
    function getProcessor(name) {
        var processor = globalConfig.processor || {};
        return processor[name];
    }

    /**
     * 当前状态设置
     *
     * @inner
     * @param {number} status
     * @param {boolean} force
     */
    function setStatus(status, force) {
        clearTimeout(cur.statusTimer);
        if (status === STATUS_LOAD && !force) {
            // 设置状态回复计时器
            // 在Action加载过久时支持用户切换页面
            cur.statusTimer = setTimeout(
                function () {
                    cur.status = STATUS_IDLE;
                },
                globalConfig.timeout
            );
        }

        cur.status = status;
    }

    /**
     * action加载完成
     *
     * @inner
     */
    function finishLoad() {
        // 设置状态为空闲
        setStatus(STATUS_IDLE);
        // 尝试加载之前被阻塞的action
        tryLoadAction();
    }

    /**
     * 清除所有的缓存action
     *
     * @inner
     */
    function clearCache() {
        var action;
        Object.keys(cachedAction).forEach(function (path) {
            action = cachedAction[path];
            if (cur.action !== action) {
                action.dispose();
            }
        });
        cachedAction = {};
        viewport.delCache();
    }

    /**
     * 删除缓存的action
     *
     * @inner
     * @param {string} path
     */
    function delCache(path) {
        var action = cachedAction[path];
        if (!action) {
            return;
        }
        // 如果不是当前显示的action则进行dispose
        // 如果是当前显示的action，后续leave会处理
        if (cur.action !== action) {
            action.dispose();
        }
        delete cachedAction[path];
        viewport.delCache(path);
    }

    /**
     * 保存当前Action相关信息
     *
     * @inner
     * @param {Action} action
     * @param {Object} route
     * @param {Page} page
     */
    function dumpInfo(action, route, page) {
        if (action) {
            cur.action = action;
            if (route.cached) {
                cachedAction[route.path] = action;
            }
        }
        else {
            cur.action = null;
        }

        cur.route = route;
        cur.page = page;
        cur.path = route.path;
    }

    /**
     * 启动Action
     *
     * @inner
     * @param {Object} config 路由信息
     * @param {string} config.path 请求路径
     * @param {Object} config.action action配置
     * @param {Object} config.query 查询条件
     * @param {boolean=} config.cached 是否缓存action
     * @param {Object=} config.transition 转场配置
     * @param {Object} config.options 跳转参数
     * @param {boolean} config.options.force 强制跳转
     * @param {boolean=} config.optins.noCache 不使用缓存action
     * @param {Object} action
     */
    function enterAction(config, action) {
        var options = config.options || {};

        // 获取页面转场配置参数
        var transition = config.transition || {};
        // 调用全局配置中的处理函数进行转场参数处理
        var processor = getProcessor('transition');
        if (processor) {
            extend(transition, processor(config, cur.route) || {});
        }

        // 如果请求路径没有变化
        // 取消转场效果
        if (config.path === cur.path) {
            transition.type = false;
        }

        var page = viewport.load(
            config.path,
            {
                cached: config.cached,
                noCache: options.noCache
            }
        );

        /**
         * 触发全局事件
         *
         * @inner
         * @type {string} eventName 事件名称
         */
        var fireEvent = (
            function (eventArgBack, eventArgFront) {
                return function (eventName) {
                    exports.emit(eventName, eventArgBack, eventArgFront);
                };
            }
        )(
            {
                route: extend({}, config),
                action: action,
                page: page
            },
            {
                route: extend({}, cur.route),
                action: cur.action,
                page: cur.page
            }
        );

        // 在转场结束时触发afterlaod事件
        page.on('afterenter', curry(fireEvent, 'afterload'));
        // 触发beforeload事件
        fireEvent('beforeload');

        /**
         * 开始转场动画
         *
         * @inner
         * @param {boolean} error
         * @return {Promise}
         */
        function startTransition(error) {
            // 转场开始前 设置强制设置为加载状态
            // 清除状态重置定时器，防止干扰转场动画
            setStatus(STATUS_LOAD, true);
            // 触发`beforetransition`
            fireEvent('beforetransition');

            dumpInfo(!error && action, config, page);

            if (error) {
                return page
                        .enter(transition.type, transition)
                        .then(bind(Resolver.rejected, Resolver));
            }
            return page.enter(transition.type, transition);
        }

        var method;
        var delayMethods = ['complete'];
        var args = [config.path, config.query, config.params, options];

        /**
         * action加载失败处理
         *
         * @inner
         * @return {Promise}
         */
        function enterFail() {
            fireEvent('error');
            return startTransition(true);
        }

        /**
         * 转场正常完成处理
         *
         * @inner
         */
        function finishTransition() {
            delayMethods.forEach(function (name) {
                action[name]();
            });
            finishLoad();
        }

        // 如果没有缓存则使用enter
        // 否则使用wakeup
        if (!cachedAction[config.path]) {
            method = 'enter';
            // 补充enter参数
            args.unshift(page.main);
            // 没有缓存时还需要ready调用
            delayMethods.unshift('ready');
        }
        else {
            method = 'wakeup';
            // 已缓存时需要调用revived
            delayMethods.unshift('revived');
        }

        // 开始加载action
        action[method]
            .apply(action, args)
            // 开始转场操作
            .then(startTransition, enterFail)
            // 转场完成处理
            .then(finishTransition, finishLoad);
    }

    /**
     * 加载Action
     *
     * @inner
     * @param {Object} config 路由信息
     * @param {string} config.path 请求路径
     * @param {Object} config.action action配置
     * @param {Object} config.query 查询条件
     * @param {Object} config.params 路径参数
     * @param {boolean=} config.cached 是否缓存action
     * @param {Object=} config.transition 转场配置
     * @param {Object} config.options 跳转参数
     * @param {boolean} config.options.force 强制跳转
     * @param {boolean=} config.optins.noCache 不使用缓存action
     */
    function loadAction(config) {
        var options = config.options || {};

        // 如果路径未发生变化
        // 只需要刷新当前action
        if (config.path === cur.path
            && !options.force
            && cur.action // 会有存在cur.path但不存在cur.action的情况，比如action加载失败
            && cur.action.refresh
        ) {
            var ret = cur.action.refresh(config.query, config.params, config.options);
            // 兼容refresh同步的情况
            if (!ret || typeof ret.then !== 'function') {
                finishLoad();
            }
            else {
                ret.then(finishLoad);
            }
            return;
        }

        if (options.noCache) {
            delCache(config.path);
        }

        // 处理当前正在工作的action
        if (cur.action) {
            cur.action[cachedAction[cur.path] ? 'sleep' : 'leave']();
        }

        // 首先尝试从cache中取action
        var action = cachedAction[config.path];

        // 没有从cache中获取到action就创建
        if (!action) {
            mm.create(config.action).then(curry(enterAction, config));
        }
        else {
            enterAction(config, action);
        }
    }

    /**
     * 执行filter
     *
     * @inner
     * @param {Object} route 路由信息
     * @return {Promise}
     */
    function executeFilter(route) {
        var resolver = new Resolver();
        var index = 0;

        /**
         * 跳过后续的filter
         * 如果不带参数则跳过剩余所有的filter
         *
         * @inner
         * @param {number} num 跳过后续filter的数量
         */
        function jump(num) {
            index += num || filters.length;
            next();
        }

        /**
         * 执行下一个filter
         *
         * @inner
         */
        function next() {
            var item = filters[index++];

            if (!item) {
                resolver.resolve(route);
            }
            else if (!item.url
                || (item.url instanceof RegExp && item.url.test(route.path))
                || item.url === route.path
            ) {
                item.filter(route, next, jump);
            }
            else {
                next();
            }
        }

        next();

        return resolver.promise();
    }

    /**
     * 首屏渲染
     *
     * @inner
     * @param {Page} page
     * @param {Object} action
     */
    function loadFirstScreen(page, action) {
        var route = waitingRoute;
        if (!route) {
            return;
        }

        function fireEvent(eventName) {
            exports.emit(
                eventName,
                {
                    route: null,
                    action: null,
                    page: null
                },
                {
                    route: route,
                    action: action,
                    page: page
                }
            );
        }

        fireEvent('beforeload');

        action.set(route.path);
        // 视图与数据已经ready了
        // 跳过enter
        action.view.set(page.main);
        // 使用初始化数据填充首屏model
        var initialData = window[globalConfig.initialDataKey];
        if (initialData) {
            action.model.fill(initialData);
        }
        action.model.set(route.query, route.params, route.path)

        fireEvent('beforetransition');

        action.ready(true);
        action.complete();

        fireEvent('afterload');

        dumpInfo(action, route, page);

        waitingRoute = null;

        finishLoad();
    }

    /**
     * 尝试加载Action
     *
     * @inner
     */
    function tryLoadAction() {
        // 如果没有待加载的路由信息
        // 或者当前不是空闲状态
        // 都不再继续加载Action
        if (!waitingRoute || cur.status !== STATUS_IDLE) {
            return;
        }

        // 设置当前状态为正在加载中
        setStatus(STATUS_LOAD);

        var path = waitingRoute.path;

        // 首屏渲染逻辑
        // 第一次加载action且能获取到起始页面
        var page;
        if (globalConfig.firstScreen && !cur.action
            && (page = viewport.front(path, {cached: waitingRoute.cached}))
        ) {
            mm.create(waitingRoute.action).then(curry(loadFirstScreen, page));
            return;
        }

        // 处理filter的执行结果
        function beforeLoad(route) {
            // 如果改变了path则以静默形式重新加载
            if (path !== route.path) {
                // 设置状态为空闲以支持跳转
                setStatus(STATUS_IDLE);
                globalConfig.router.redirect(route.path, route.query, route.options);
            }
            else {
                loadAction(route);
            }
        }

        // 执行filter
        executeFilter(waitingRoute).then(beforeLoad);
        // 进入加载流程了
        // 清除等待加载的route信息
        waitingRoute = null;
    }

    /**
     * 路由导向
     *
     * @inner
     * @param {option} config 路由配置
     * @param {string} path 请求路径
     * @param {Object} query 查询条件
     * @param {Object} params 路径参数
     * @param {string} url 完整的URL
     * @param {Object} options 跳转参数
     */
    function routeTo(config, path, query, params, url, options) {
        // 设置当前的路由信息
        waitingRoute = extend({}, config);
        waitingRoute.path = path;
        waitingRoute.query = query;
        waitingRoute.params = params;
        waitingRoute.options = options;
        waitingRoute.url = url;

        // 尝试加载Action
        tryLoadAction();
    }

    /**
     * 扩展全局配置项
     *
     * @inner
     * @param {Object} options 配置项
     * @return {Object}
     */
    function extendGlobalConfig(options) {
        var config = extend(globalConfig, options);

        if (!Array.isArray(config.template)) {
            config.template = [config.template];
        }

        // 如果没有指定路由器则使用默认的hash路由
        if (!config.router) {
            config.router = require('saber-router');
            config.router.controller(require('saber-router/controller/hash'));
        }

        return config;
    }

    var routes = [];
    var exports = {};

    Emitter.mixin(exports);

    /**
     * 加载path配置信息
     *
     * @public
     * @param {Object} paths
     */
    exports.load = function (paths) {
        if (!Array.isArray(paths)) {
            paths = [paths];
        }

        // 如果还没有制定router
        // 则先缓存路由信息
        // 否则直接添加路由
        if (!globalConfig.router) {
            routes = routes.concat(paths);
        }
        else {
            paths.forEach(function (item) {
                globalConfig.router.add(item.path, curry(routeTo, item));
            });
        }
    };

    /**
     * 启动框架
     *
     * @public
     * @param {HTMLElement} main
     * @param {Object} options 全局配置信息 完整配置参考`./config.js`
     */
    exports.start = function (main, options) {
        // 扩展全局配置信息
        var config = extendGlobalConfig(options);

        var router = config.router;

        mm.config({
            template: config.template,
            templateConfig: config.templateConfig,
            templateData : config.templateData,
            router: router,
            Presenter: config.Presenter,
            View: config.View,
            Model: config.Model
        });

        // 初始化viewport
        viewport.init(main, config.viewport);

        // 启用无延迟点击
        Tap.mixin(document.body);

        // 添加路由
        routes.forEach(function (item) {
            router.add(item.path, curry(routeTo, item));
        });

        router.config({
            path: config.path,
            index: config.index
        });

        // 启动路由
        router.start();
    };

    /**
     * 添加filter
     *
     * @public
     * @param {string|RegExp=} url
     * @param {Function} filter
     */
    exports.addFilter = function (url, filter) {
        if (arguments.length === 1) {
            filter = url;
            url = null;
        }

        filters.push({
            url: url,
            filter: filter
        });
    };

    /**
     * 删除filter
     *
     * @public
     * @param {string|RegExp=} url
     */
    exports.removeFilter = function (url) {
        if (!url) {
            filters = [];
        }
        else {
            var index;
            var res = filters.some(function (item, i) {
                index = i;
                return item.url.toString() === url.toString();
            });
            if (res) {
                filters.splice(index, 1);
            }
        }
    };

    /**
     * 删除缓存的action
     *
     * @public
     * @param {string} path
     */
    exports.delCachedAction = function (path) {
        if (path) {
            delCache(path);
        }
        else {
            clearCache();
        }
    };

    /**
     * 停止App
     * For Test
     *
     * @public
     */
    exports.stop = function () {
        var router = globalConfig.router;

        router.stop();
        router.clear();
        routes = [];

        filters = [];
        waitingRoute = null;
        cur = {status: STATUS_IDLE};

        exports.delCachedAction();
    };

    return exports;

});
<|endoftext|>


<commit_before>var taskContainer = document.getElementById('task-area')
var taskSwitcherButton = document.getElementById('switch-task-button')
var addTaskButton = document.getElementById('add-task')
var addTaskLabel = addTaskButton.querySelector('span')
var taskOverlayNavbar = document.getElementById('task-overlay-navbar')

taskSwitcherButton.title = l('viewTasks')
addTaskLabel.textContent = l('newTask')

function addTask () {
  tasks.setSelected(tasks.add())
  taskOverlay.hide()

  rerenderTabstrip()
  addTab()
}

taskSwitcherButton.addEventListener('click', function () {
  taskOverlay.toggle()
})

addTaskButton.addEventListener('click', function (e) {
  switchToTask(tasks.add())
  taskOverlay.hide()
})

taskOverlayNavbar.addEventListener('click', function () {
  taskOverlay.hide()
})

var dragula = require('dragula')

var taskOverlay = {
  overlayElement: document.getElementById('task-overlay'),
  isShown: false,
  tabDragula: dragula({
    direction: 'vertical'
  }),
  taskDragula: dragula({
    direction: 'vertical',
    containers: [taskContainer],
    moves: function (el, source, handle, sibling) {
      // ignore drag events that come from a tab element, since they will be handled by the other dragula instance
      // also ignore inputs, since using them as drag handles breaks text selection
      var n = handle
      while (n) {
        if (n.classList.contains('task-tab-item') || n.tagName === 'INPUT') {
          return false
        }
        n = n.parentElement
      }
      return true
    }
  }),
  show: function () {
    /* disabled in focus mode */
    if (isFocusMode) {
      showFocusModeError()
      return
    }

    document.body.classList.add('task-overlay-is-shown')

    leaveTabEditMode()

    this.isShown = true
    taskSwitcherButton.classList.add('active')

    this.tabDragula.containers = []

    // show the task elements
    tasks.get().forEach(function (task, index) {
      var el = window.task_container_build_func(task, index)

      taskContainer.appendChild(el)
      taskOverlay.tabDragula.containers.push(el.getElementsByClassName('task-tabs-container')[0])
    })

    // scroll to the selected element and focus it

    var currentTabElement = document.querySelector('.task-tab-item[data-tab="{id}"]'.replace('{id}', currentTask.tabs.getSelected()))

    if (currentTabElement) {
      currentTabElement.scrollIntoViewIfNeeded()
      currentTabElement.classList.add('fakefocus')
    }

    // un-hide the overlay
    this.overlayElement.hidden = false
  },

  hide: function () {
    if (this.isShown) {
      this.isShown = false
      this.overlayElement.hidden = true

      //wait until the animation is complete to remove the tab elements
      setTimeout(function () {
        empty(taskContainer)
      }, 200);

      document.body.classList.remove('task-overlay-is-shown')

      // if the current tab has been deleted, switch to the most recent one

      if (!tabs.getSelected()) {
        var mostRecentTab = tabs.get().sort(function (a, b) {
          return b.lastActivity - a.lastActivity
        })[0]

        if (mostRecentTab) {
          switchToTab(mostRecentTab.id)
        }
      }

      taskSwitcherButton.classList.remove('active')
    }
  },

  toggle: function () {
    if (this.isShown) {
      this.hide()
    } else {
      this.show()
    }
  }
}

// swipe down on the tabstrip to show the task overlay
// this was the old expanded mode gesture, so it's remapped to the overlay
navbar.addEventListener('mousewheel', function (e) {
  if (e.deltaY < -30 && e.deltaX < 10) {
    taskOverlay.show()
    e.stopImmediatePropagation()
  }
})

function getTaskContainer (id) {
  return document.querySelector('.task-container[data-task="{id}"]'.replace('{id}', id))
}

/* rearrange tabs when they are dropped */

taskOverlay.tabDragula.on('drop', function (el, target, source, sibling) { // see https://github.com/bevacqua/dragula#drakeon-events
  var tabId = el.getAttribute('data-tab')
  if (sibling) {
    var adjacentTadId = sibling.getAttribute('data-tab')
  }

  var previousTask = tasks.get(source.getAttribute('data-task'))
  var newTask = tasks.get(target.getAttribute('data-task'))

  // remove tab from old task
  var oldTab = previousTask.tabs.splice(previousTask.tabs.getIndex(tabId), 1)[0]

  // if the old task has no tabs left in it, destroy it

  if (previousTask.tabs.length === 0) {
    closeTask(previousTask.id)
    getTaskContainer(previousTask.id).remove()
  }

  // find where in the new task the tab should be inserted
  if (adjacentTadId) {
    var newIdx = newTask.tabs.getIndex(adjacentTadId)
  } else {
    // tab was inserted at end
    var newIdx = newTask.tabs.length
  }

  // insert the tab at the correct spot
  newTask.tabs.splice(newIdx, 0, oldTab)
})

/* rearrange tasks when they are dropped */

taskOverlay.taskDragula.on('drop', function (el, target, source, sibling) {
  var droppedTaskId = el.getAttribute('data-task')
  if (sibling) {
    var adjacentTaskId = sibling.getAttribute('data-task')
  }

  // remove the task from the tasks list
  var droppedTask = tasks.splice(tasks.getIndex(droppedTaskId), 1)[0]

  // find where it should be inserted
  if (adjacentTaskId) {
    var newIdx = tasks.getIndex(adjacentTaskId)
  } else {
    var newIdx = tasks.length
  }

  // reinsert the task
  tasks.splice(newIdx, 0, droppedTask)
})
<commit_msg>fix bugs introduced by 52141760c0cff7500805419431f29464c1cefbdf<commit_after>var taskContainer = document.getElementById('task-area')
var taskSwitcherButton = document.getElementById('switch-task-button')
var addTaskButton = document.getElementById('add-task')
var addTaskLabel = addTaskButton.querySelector('span')
var taskOverlayNavbar = document.getElementById('task-overlay-navbar')

taskSwitcherButton.title = l('viewTasks')
addTaskLabel.textContent = l('newTask')

function addTask () {
  tasks.setSelected(tasks.add())
  taskOverlay.hide()

  rerenderTabstrip()
  addTab()
}

taskSwitcherButton.addEventListener('click', function () {
  taskOverlay.toggle()
})

addTaskButton.addEventListener('click', function (e) {
  switchToTask(tasks.add())
  taskOverlay.hide()
})

taskOverlayNavbar.addEventListener('click', function () {
  taskOverlay.hide()
})

var dragula = require('dragula')

var taskOverlay = {
  overlayElement: document.getElementById('task-overlay'),
  isShown: false,
  tabDragula: dragula({
    direction: 'vertical'
  }),
  taskDragula: dragula({
    direction: 'vertical',
    containers: [taskContainer],
    moves: function (el, source, handle, sibling) {
      // ignore drag events that come from a tab element, since they will be handled by the other dragula instance
      // also ignore inputs, since using them as drag handles breaks text selection
      var n = handle
      while (n) {
        if (n.classList.contains('task-tab-item') || n.tagName === 'INPUT') {
          return false
        }
        n = n.parentElement
      }
      return true
    }
  }),
  show: function () {
    /* disabled in focus mode */
    if (isFocusMode) {
      showFocusModeError()
      return
    }

    document.body.classList.add('task-overlay-is-shown')

    leaveTabEditMode()

    this.isShown = true
    taskSwitcherButton.classList.add('active')

    this.tabDragula.containers = []
    empty(taskContainer)

    // show the task elements
    tasks.get().forEach(function (task, index) {
      var el = window.task_container_build_func(task, index)

      taskContainer.appendChild(el)
      taskOverlay.tabDragula.containers.push(el.getElementsByClassName('task-tabs-container')[0])
    })

    // scroll to the selected element and focus it

    var currentTabElement = document.querySelector('.task-tab-item[data-tab="{id}"]'.replace('{id}', currentTask.tabs.getSelected()))

    if (currentTabElement) {
      currentTabElement.scrollIntoViewIfNeeded()
      currentTabElement.classList.add('fakefocus')
    }

    // un-hide the overlay
    this.overlayElement.hidden = false
  },

  hide: function () {
    if (this.isShown) {
      this.isShown = false
      this.overlayElement.hidden = true

      //wait until the animation is complete to remove the tab elements
      setTimeout(function () {
        if (!taskOverlay.isShown) {
          empty(taskContainer)
        }
      }, 200);

      document.body.classList.remove('task-overlay-is-shown')

      // if the current tab has been deleted, switch to the most recent one

      if (!tabs.getSelected()) {
        var mostRecentTab = tabs.get().sort(function (a, b) {
          return b.lastActivity - a.lastActivity
        })[0]

        if (mostRecentTab) {
          switchToTab(mostRecentTab.id)
        }
      }

      taskSwitcherButton.classList.remove('active')
    }
  },

  toggle: function () {
    if (this.isShown) {
      this.hide()
    } else {
      this.show()
    }
  }
}

// swipe down on the tabstrip to show the task overlay
// this was the old expanded mode gesture, so it's remapped to the overlay
navbar.addEventListener('mousewheel', function (e) {
  if (e.deltaY < -30 && e.deltaX < 10) {
    taskOverlay.show()
    e.stopImmediatePropagation()
  }
})

function getTaskContainer (id) {
  return document.querySelector('.task-container[data-task="{id}"]'.replace('{id}', id))
}

/* rearrange tabs when they are dropped */

taskOverlay.tabDragula.on('drop', function (el, target, source, sibling) { // see https://github.com/bevacqua/dragula#drakeon-events
  var tabId = el.getAttribute('data-tab')
  if (sibling) {
    var adjacentTadId = sibling.getAttribute('data-tab')
  }

  var previousTask = tasks.get(source.getAttribute('data-task'))
  var newTask = tasks.get(target.getAttribute('data-task'))

  // remove tab from old task
  var oldTab = previousTask.tabs.splice(previousTask.tabs.getIndex(tabId), 1)[0]

  // if the old task has no tabs left in it, destroy it

  if (previousTask.tabs.length === 0) {
    closeTask(previousTask.id)
    getTaskContainer(previousTask.id).remove()
  }

  // find where in the new task the tab should be inserted
  if (adjacentTadId) {
    var newIdx = newTask.tabs.getIndex(adjacentTadId)
  } else {
    // tab was inserted at end
    var newIdx = newTask.tabs.length
  }

  // insert the tab at the correct spot
  newTask.tabs.splice(newIdx, 0, oldTab)
})

/* rearrange tasks when they are dropped */

taskOverlay.taskDragula.on('drop', function (el, target, source, sibling) {
  var droppedTaskId = el.getAttribute('data-task')
  if (sibling) {
    var adjacentTaskId = sibling.getAttribute('data-task')
  }

  // remove the task from the tasks list
  var droppedTask = tasks.splice(tasks.getIndex(droppedTaskId), 1)[0]

  // find where it should be inserted
  if (adjacentTaskId) {
    var newIdx = tasks.getIndex(adjacentTaskId)
  } else {
    var newIdx = tasks.length
  }

  // reinsert the task
  tasks.splice(newIdx, 0, droppedTask)
})
<|endoftext|>


<commit_before>/*
 *  E5.1 updated the algorithm in Section 10.5, declaration binding instantiation.
 *  Step 5.e is a new step (compared to E5) and restores compatibility to an E3
 *  idiom.  See E5.1 Annex F.
 *
 *  This testcase tests for the the updated E5.1 behavior for eval() calls.
 *  Eval creates configurable bindings.
 *
 *  Tests for Program execution context are in individual separate files,
 *  because function declarations are "hoisted" to program entry.  Thus,
 *  controlling test execution is a bit tricky.  Program code creates
 *  non-configurable bindings.
 *
 *  V8 does not pass these tests.
 */

/*
 *  Notes:
 *
 *    - The new behavior only applies if a function is being re-declared in the
 *      global environment record.  This is the case with:
 *
 *      + Program code
 *      + Indirect eval
 *
 *    - An existing property of the same name exists in the global object.
 *      Such a name can be:
 *
 *      + Another function
 *      + Any other value (like a constant)
 *
 *    - The existing property may have various attribute combinations, and may
 *      even be an accessor (set/get).
 *
 *    - The purpose of step 5.e is to ensure that the resulting property is
 *      a plain data property, with [[Writable]] and [[Enumerable]] true;
 *      [[Configurable]] final value depends on execution context.
 *
 *      + If configurable, the property is converted to a plain data property
 *        and [[Writable]] and [[Enumerable]] are set to true.  [[Configurable]]
 *        is set based on whether configurable bindings are enabled.
 *
 *      + If not configurable, the property must be a plain data property and
 *        have [[Writable]] and [[Enumerable]] set.  If not, a TypeError occurs.
 *
 *    - Note that re-declaring a global function automatically makes the property
 *      enumerable even if it earlier wasn't.
 */

function printProps(name) {
    var indirectEval = eval;
    var global = indirectEval('this;');  // indirect eval -> this = global object
    var desc = Object.getOwnPropertyDescriptor(global, name);
    if (desc.get || desc.set) {
        print(name + ': ' +
              'typeof=' + typeof global[name] + ', ' +
              'enumerable=' + desc.enumerable + ', ' +
              'configurable=' + desc.configurable);
    } else {
        print(name + ': ' +
              'typeof=' + typeof global[name] + ', ' +
              'writable=' + desc.writable + ', ' +
              'enumerable=' + desc.enumerable + ', ' +
              'configurable=' + desc.configurable);
    }
}

function defineProp(name, attrs) {
    var indirectEval = eval;
    var global = indirectEval('this;');  // indirect eval -> this = global object
    Object.defineProperty(global, name, attrs);
}

/*===
RegExp: typeof=function, writable=true, enumerable=false, configurable=true
123
RegExp: typeof=function, writable=true, enumerable=true, configurable=true
===*/

/* Existing configurable data property.
 *
 * Note that the property should become enumerable (but does not in current
 * V8).
 */

printProps('RegExp');

try {
    eval("function RegExp() { return 123; }");
    print(RegExp());
} catch (e) {
    print(e.name);
}

printProps('RegExp');

/*===
NaN: typeof=number, writable=false, enumerable=false, configurable=false
TypeError
NaN: typeof=function, writable=true, enumerable=true, configurable=true
===*/

/* Existing non-configurable data property. */

printProps('NaN');

try {
    // non-configurable and does not match requirements of step 5.e.iv -> TypeError */
    eval("function NaN() { return 234; }");
    print('never here');
} catch (e) {
    print(e.name);
}

printProps('NaN');

/*===
configurableAccessor: typeof=undefined, enumerable=false, configurable=true
345
configurableAccessor: typeof=function, writable=true, enumerable=true, configurable=true
===*/

/* Existing configurable accessor.
 *
 * Need to create ourselves.
 */

defineProp('configurableAccessor', {
    get: function() { print('getter'); },
    set: function(x) { print('setter'); },
    enumerable: false,
    configurable: true
})

printProps('configurableAccessor');

try {
    eval("function configurableAccessor() { return 345; }");
    print(configurableAccessor());
} catch (e) {
    print(e.name);
}

printProps('configurableAccessor');

/*===
nonconfigurableAccessor: typeof=undefined, enumerable=false, configurable=false
TypeError
nonconfigurableAccessor: typeof=undefined, enumerable=false, configurable=false
===*/

/* Existing non-configurable accessor. */

defineProp('nonconfigurableAccessor', {
    get: function() { print('getter'); },
    set: function(x) { print('setter'); },
    enumerable: false,
    configurable: false,
})

printProps('nonconfigurableAccessor');

try {
    eval("function nonconfigurableAccessor() { return 456; }");
} catch (e) {
    print(e.name);
}

printProps('nonconfigurableAccessor');

<commit_msg>fix global redeclaration test case expected results<commit_after>/*
 *  E5.1 updated the algorithm in Section 10.5, declaration binding instantiation.
 *  Step 5.e is a new step (compared to E5) and restores compatibility to an E3
 *  idiom.  See E5.1 Annex F.
 *
 *  This testcase tests for the the updated E5.1 behavior for eval() calls.
 *  Eval creates configurable bindings.
 *
 *  Tests for Program execution context are in individual separate files,
 *  because function declarations are "hoisted" to program entry.  Thus,
 *  controlling test execution is a bit tricky.  Program code creates
 *  non-configurable bindings.
 *
 *  V8 does not pass these tests.
 */

/*
 *  Notes:
 *
 *    - The new behavior only applies if a function is being re-declared in the
 *      global environment record.  This is the case with:
 *
 *      + Program code
 *      + Indirect eval
 *
 *    - An existing property of the same name exists in the global object.
 *      Such a name can be:
 *
 *      + Another function
 *      + Any other value (like a constant)
 *
 *    - The existing property may have various attribute combinations, and may
 *      even be an accessor (set/get).
 *
 *    - The purpose of step 5.e is to ensure that the resulting property is
 *      a plain data property, with [[Writable]] and [[Enumerable]] true;
 *      [[Configurable]] final value depends on execution context.
 *
 *      + If configurable, the property is converted to a plain data property
 *        and [[Writable]] and [[Enumerable]] are set to true.  [[Configurable]]
 *        is set based on whether configurable bindings are enabled.
 *
 *      + If not configurable, the property must be a plain data property and
 *        have [[Writable]] and [[Enumerable]] set.  If not, a TypeError occurs.
 *
 *    - Note that re-declaring a global function automatically makes the property
 *      enumerable even if it earlier wasn't.
 */

function printProps(name) {
    var indirectEval = eval;
    var global = indirectEval('this;');  // indirect eval -> this = global object
    var desc = Object.getOwnPropertyDescriptor(global, name);
    if (desc.get || desc.set) {
        // Note: typeof will invoke getter here
        print(name + ': ' +
              'typeof=' + typeof global[name] + ', ' +
              'enumerable=' + desc.enumerable + ', ' +
              'configurable=' + desc.configurable);
    } else {
        print(name + ': ' +
              'typeof=' + typeof global[name] + ', ' +
              'writable=' + desc.writable + ', ' +
              'enumerable=' + desc.enumerable + ', ' +
              'configurable=' + desc.configurable);
    }
}

function defineProp(name, attrs) {
    var indirectEval = eval;
    var global = indirectEval('this;');  // indirect eval -> this = global object
    Object.defineProperty(global, name, attrs);
}

/*===
RegExp: typeof=function, writable=true, enumerable=false, configurable=true
123
RegExp: typeof=function, writable=true, enumerable=true, configurable=true
===*/

/* Existing configurable data property.
 *
 * Note that the property should become enumerable (but does not in current
 * V8).
 */

printProps('RegExp');

try {
    eval("function RegExp() { return 123; }");
    print(RegExp());
} catch (e) {
    print(e.name);
}

printProps('RegExp');

/*===
NaN: typeof=number, writable=false, enumerable=false, configurable=false
TypeError
NaN: typeof=number, writable=false, enumerable=false, configurable=false
===*/

/* Existing non-configurable data property. */

printProps('NaN');

try {
    // non-configurable and does not match requirements of step 5.e.iv -> TypeError */
    eval("function NaN() { return 234; }");
    print('never here');
} catch (e) {
    print(e.name);
}

printProps('NaN');

/*===
getter
configurableAccessor: typeof=undefined, enumerable=false, configurable=true
345
configurableAccessor: typeof=function, writable=true, enumerable=true, configurable=true
===*/

/* Existing configurable accessor.
 *
 * Need to create ourselves.
 */

defineProp('configurableAccessor', {
    get: function() { print('getter'); },
    set: function(x) { print('setter'); },
    enumerable: false,
    configurable: true
})

// Note: printProps() will invoke getter here (but not below)

printProps('configurableAccessor');

try {
    eval("function configurableAccessor() { return 345; }");
    print(configurableAccessor());
} catch (e) {
    print(e.name);
}

printProps('configurableAccessor');

/*===
getter
nonconfigurableAccessor: typeof=undefined, enumerable=false, configurable=false
TypeError
getter
nonconfigurableAccessor: typeof=undefined, enumerable=false, configurable=false
===*/

/* Existing non-configurable accessor. */

defineProp('nonconfigurableAccessor', {
    get: function() { print('getter'); },
    set: function(x) { print('setter'); },
    enumerable: false,
    configurable: false,
})

// Note: printProps() will invoke getter here and again below

printProps('nonconfigurableAccessor');

try {
    eval("function nonconfigurableAccessor() { return 456; }");
} catch (e) {
    print(e.name);
}

printProps('nonconfigurableAccessor');

<|endoftext|>


<commit_before>window.ReflectionVariable = require('./ReflectionVariable');
window.ReflectionAssignedFunctionExpression = require('./ReflectionAssignedFunctionExpression');
window.ReflectionFunctionExpression = require('./ReflectionFunctionExpression');
window.ReflectionFunctionDeclaration = require('./ReflectionFunctionDeclaration');
window.ReflectionFunction = require('./ReflectionFunction');
window.ReflectionScopeAbstract = require('./ReflectionScopeAbstract');
window.ReflectionFile = require('./ReflectionFile');
window.Reflection = require('./Reflection');
<commit_msg>Replace the uses of window with global<commit_after>global.ReflectionVariable = require('./ReflectionVariable');
global.ReflectionAssignedFunctionExpression = require('./ReflectionAssignedFunctionExpression');
global.ReflectionFunctionExpression = require('./ReflectionFunctionExpression');
global.ReflectionFunctionDeclaration = require('./ReflectionFunctionDeclaration');
global.ReflectionFunction = require('./ReflectionFunction');
global.ReflectionScopeAbstract = require('./ReflectionScopeAbstract');
global.ReflectionFile = require('./ReflectionFile');
global.Reflection = require('./Reflection');
<|endoftext|>


<commit_before>import neo4j from 'neo4j'
import ResultObserver from './result-observer.js'
import { cypherToNative, nativeToCypher } from './cypher-native-binders.js'
import { shouldRunTest } from './skipped-tests'

export function NewDriver (context, data, { writeResponse }) {
  const {
    uri,
    authorizationToken: { data: authToken },
    userAgent,
    resolverRegistered
  } = data
  const resolver = resolverRegistered
    ? address =>
      new Promise((resolve, reject) => {
        const id = context.addResolverRequest(resolve, reject)
        writeResponse('ResolverResolutionRequired', { id, address })
      })
    : undefined
  const driver = neo4j.driver(uri, authToken, {
    userAgent,
    resolver,
    useBigInt: true,
    logging: neo4j.logging.console(process.env.LOG_LEVEL)
  })
  const id = context.addDriver(driver)
  writeResponse('Driver', { id })
}

export function DriverClose (context, data, wire) {
  const { driverId } = data
  const driver = context.getDriver(driverId)
  driver
    .close()
    .then(() => {
      wire.writeResponse('Driver', { id: driverId })
    })
    .catch(err => wire.writeError(err))
  context.removeDriver(driverId)
}

export function NewSession (context, data, wire) {
  let { driverId, accessMode, bookmarks, database, fetchSize } = data
  switch (accessMode) {
    case 'r':
      accessMode = neo4j.session.READ
      break
    case 'w':
      accessMode = neo4j.session.WRITE
      break
    default:
      wire.writeBackendError('Unknown accessmode: ' + accessMode)
      return
  }
  const driver = context.getDriver(driverId)
  const session = driver.session({
    defaultAccessMode: accessMode,
    bookmarks,
    database,
    fetchSize
  })
  const id = context.addSession(session)
  wire.writeResponse('Session', { id })
}

export function SessionClose (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .close()
    .then(() => {
      wire.writeResponse('Session', { id: sessionId })
    })
    .catch(err => wire.writeError(err))
}

export function SessionRun (context, data, wire) {
  const { sessionId, cypher, params, txMeta: metadata, timeout } = data
  const session = context.getSession(sessionId)
  if (params) {
    for (const [key, value] of Object.entries(params)) {
      params[key] = cypherToNative(value)
    }
  }

  const observers = context.getResultObserversBySessionId(sessionId)

  Promise.all(observers.map(obs => obs.completitionPromise()))
    .catch(_ => null)
    .then(_ => {
      const result = session.run(cypher, params, { metadata, timeout })
      const resultObserver = new ResultObserver({ sessionId, result })
      result.subscribe(resultObserver)
      const id = context.addResultObserver(resultObserver)
      wire.writeResponse('Result', { id })
    })
}

export function ResultNext (context, data, wire) {
  const { resultId } = data
  const resultObserver = context.getResultObserver(resultId)
  const nextPromise = resultObserver.next()
  nextPromise
    .then(rec => {
      if (rec) {
        const values = Array.from(rec.values()).map(nativeToCypher)
        wire.writeResponse('Record', {
          values: values
        })
      } else {
        wire.writeResponse('NullRecord', null)
      }
    })
    .catch(e => {
      console.log('got some err: ' + JSON.stringify(e))
      wire.writeError(e)
    })
}

export function ResultConsume (context, data, wire) {
  const { resultId } = data
  const resultObserver = context.getResultObserver(resultId)
  resultObserver
    .completitionPromise()
    .then(summary => {
      wire.writeResponse('Summary', {
        ...summary,
        serverInfo: {
          agent: summary.server.agent,
          protocolVersion: summary.server.protocolVersion.toFixed(1)
        }
      })
    })
    .catch(e => wire.writeError(e))
}

export function SessionReadTransaction (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .readTransaction(
      tx =>
        new Promise((resolve, reject) => {
          const id = context.addTx(tx, sessionId, resolve, reject)
          wire.writeResponse('RetryableTry', { id })
        })
    )
    .then(_ => wire.writeResponse('RetryableDone', null))
    .catch(error => wire.writeError(error))
}

export function TransactionRun (context, data, wire) {
  const { txId, cypher, params } = data
  const tx = context.getTx(txId)
  if (params) {
    for (const [key, value] of Object.entries(params)) {
      params[key] = cypherToNative(value)
    }
  }
  const result = tx.tx.run(cypher, params)
  const resultObserver = new ResultObserver({ result })
  result.subscribe(resultObserver)
  const id = context.addResultObserver(resultObserver)
  wire.writeResponse('Result', { id })
}

export function RetryablePositive (context, data, wire) {
  const { sessionId } = data
  context.getTxsBySessionId(sessionId).forEach(tx => {
    tx.resolve()
  })
}

export function RetryableNegative (context, data, wire) {
  const { sessionId, errorId } = data
  const error = context.getError(errorId) || new Error('Client error')
  context.getTxsBySessionId(sessionId).forEach(tx => {
    tx.reject(error)
  })
}

export function SessionBeginTransaction (context, data, wire) {
  const { sessionId, txMeta: metadata, timeout } = data
  const session = context.getSession(sessionId)
  const tx = session.beginTransaction({ metadata, timeout })
  const id = context.addTx(tx, sessionId)
  wire.writeResponse('Transaction', { id })
}

export function TransactionCommit (context, data, wire) {
  const { txId: id } = data
  const { tx } = context.getTx(id)
  tx.commit()
    .then(() => wire.writeResponse('Transaction', { id }))
    .catch(e => {
      console.log('got some err: ' + JSON.stringify(e))
      wire.writeError(e)
    })
}

export function TransactionRollback (context, data, wire) {
  const { txId: id } = data
  const { tx } = context.getTx(id)
  tx.rollback()
    .then(() => wire.writeResponse('Transaction', { id }))
    .catch(e => wire.writeError(e))
}

export function SessionLastBookmarks (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  const bookmarks = session.lastBookmark()
  wire.writeResponse('Bookmarks', { bookmarks })
}

export function SessionWriteTransaction (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .writeTransaction(
      tx =>
        new Promise((resolve, reject) => {
          const id = context.addTx(tx, sessionId, resolve, reject)
          wire.writeResponse('RetryableTry', { id })
        })
    )
    .then(_ => wire.writeResponse('RetryableDone', null))
    .catch(error => wire.writeError(error))
}

export function StartTest (_, { testName }, wire) {
  shouldRunTest(testName, {
    onRun: () => wire.writeResponse('RunTest', null),
    onSkip: reason => wire.writeResponse('SkipTest', { reason })
  })
}

export function GetFeatures (_context, _params, wire) {
  wire.writeResponse('FeatureList', {
    features: [
      'AuthorizationExpiredTreatment',
      'ConfHint:connection.recv_timeout_seconds'
    ]
  })
}

export function VerifyConnectivity (context, { driverId }, wire) {
  const driver = context.getDriver(driverId)
  driver
    .verifyConnectivity()
    .then(() => wire.writeResponse('Driver', { id: driverId }))
    .catch(error => wire.writeError(error))
}

export function CheckMultiDBSupport (context, { driverId }, wire) {
  const driver = context.getDriver(driverId)
  driver
    .supportsMultiDb()
    .then(available =>
      wire.writeResponse('MultiDBSupport', { id: driverId, available })
    )
    .catch(error => wire.writeError(error))
}

export function ResolverResolutionCompleted (
  context,
  { requestId, addresses },
  wire
) {
  const request = context.getResolverRequest(requestId)
  request.resolve(addresses)
}

export function GetRoutingTable (context, { driverId, database }, wire) {
  const serverAddressToString = serverAddress => serverAddress.asHostPort()
  const driver = context.getDriver(driverId)
  const routingTable =
    driver &&
    driver._getOrCreateConnectionProvider() &&
    driver._getOrCreateConnectionProvider()._routingTableRegistry &&
    driver._getOrCreateConnectionProvider()._routingTableRegistry.get(database)

  if (routingTable) {
    wire.writeResponse('RoutingTable', {
      database: routingTable.database,
      ttl: routingTable.ttl,
      readers: routingTable.readers.map(serverAddressToString),
      writers: routingTable.writers.map(serverAddressToString),
      routers: routingTable.routers.map(serverAddressToString)
    })
  } else {
    wire.writeError('Could not find routing table')
  }
}
<commit_msg>Enable auth tests in TestKit backend (#773)<commit_after>import neo4j from 'neo4j'
import ResultObserver from './result-observer.js'
import { cypherToNative, nativeToCypher } from './cypher-native-binders.js'
import { shouldRunTest } from './skipped-tests'

export function NewDriver (context, data, { writeResponse }) {
  const {
    uri,
    authorizationToken: { data: authToken },
    userAgent,
    resolverRegistered
  } = data
  let parsedAuthToken = authToken
  switch (authToken.scheme) {
    case 'basic':
      parsedAuthToken = neo4j.auth.basic(
        authToken.principal,
        authToken.credentials,
        authToken.realm
      )
      break
    case 'kerberos':
      parsedAuthToken = neo4j.auth.kerberos(authToken.credentials)
      break
    default:
      parsedAuthToken = neo4j.auth.custom(
        authToken.principal,
        authToken.credentials,
        authToken.realm,
        authToken.scheme,
        authToken.parameters
      )
  }
  const resolver = resolverRegistered
    ? address =>
      new Promise((resolve, reject) => {
        const id = context.addResolverRequest(resolve, reject)
        writeResponse('ResolverResolutionRequired', { id, address })
      })
    : undefined
  const driver = neo4j.driver(uri, parsedAuthToken, {
    userAgent,
    resolver,
    useBigInt: true,
    logging: neo4j.logging.console(process.env.LOG_LEVEL)
  })
  const id = context.addDriver(driver)
  writeResponse('Driver', { id })
}

export function DriverClose (context, data, wire) {
  const { driverId } = data
  const driver = context.getDriver(driverId)
  driver
    .close()
    .then(() => {
      wire.writeResponse('Driver', { id: driverId })
    })
    .catch(err => wire.writeError(err))
  context.removeDriver(driverId)
}

export function NewSession (context, data, wire) {
  let { driverId, accessMode, bookmarks, database, fetchSize } = data
  switch (accessMode) {
    case 'r':
      accessMode = neo4j.session.READ
      break
    case 'w':
      accessMode = neo4j.session.WRITE
      break
    default:
      wire.writeBackendError('Unknown accessmode: ' + accessMode)
      return
  }
  const driver = context.getDriver(driverId)
  const session = driver.session({
    defaultAccessMode: accessMode,
    bookmarks,
    database,
    fetchSize
  })
  const id = context.addSession(session)
  wire.writeResponse('Session', { id })
}

export function SessionClose (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .close()
    .then(() => {
      wire.writeResponse('Session', { id: sessionId })
    })
    .catch(err => wire.writeError(err))
}

export function SessionRun (context, data, wire) {
  const { sessionId, cypher, params, txMeta: metadata, timeout } = data
  const session = context.getSession(sessionId)
  if (params) {
    for (const [key, value] of Object.entries(params)) {
      params[key] = cypherToNative(value)
    }
  }

  const observers = context.getResultObserversBySessionId(sessionId)

  Promise.all(observers.map(obs => obs.completitionPromise()))
    .catch(_ => null)
    .then(_ => {
      const result = session.run(cypher, params, { metadata, timeout })
      const resultObserver = new ResultObserver({ sessionId, result })
      result.subscribe(resultObserver)
      const id = context.addResultObserver(resultObserver)
      wire.writeResponse('Result', { id })
    })
}

export function ResultNext (context, data, wire) {
  const { resultId } = data
  const resultObserver = context.getResultObserver(resultId)
  const nextPromise = resultObserver.next()
  nextPromise
    .then(rec => {
      if (rec) {
        const values = Array.from(rec.values()).map(nativeToCypher)
        wire.writeResponse('Record', {
          values: values
        })
      } else {
        wire.writeResponse('NullRecord', null)
      }
    })
    .catch(e => {
      console.log('got some err: ' + JSON.stringify(e))
      wire.writeError(e)
    })
}

export function ResultConsume (context, data, wire) {
  const { resultId } = data
  const resultObserver = context.getResultObserver(resultId)
  resultObserver
    .completitionPromise()
    .then(summary => {
      wire.writeResponse('Summary', {
        ...summary,
        serverInfo: {
          agent: summary.server.agent,
          protocolVersion: summary.server.protocolVersion.toFixed(1)
        }
      })
    })
    .catch(e => wire.writeError(e))
}

export function SessionReadTransaction (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .readTransaction(
      tx =>
        new Promise((resolve, reject) => {
          const id = context.addTx(tx, sessionId, resolve, reject)
          wire.writeResponse('RetryableTry', { id })
        })
    )
    .then(_ => wire.writeResponse('RetryableDone', null))
    .catch(error => wire.writeError(error))
}

export function TransactionRun (context, data, wire) {
  const { txId, cypher, params } = data
  const tx = context.getTx(txId)
  if (params) {
    for (const [key, value] of Object.entries(params)) {
      params[key] = cypherToNative(value)
    }
  }
  const result = tx.tx.run(cypher, params)
  const resultObserver = new ResultObserver({ result })
  result.subscribe(resultObserver)
  const id = context.addResultObserver(resultObserver)
  wire.writeResponse('Result', { id })
}

export function RetryablePositive (context, data, wire) {
  const { sessionId } = data
  context.getTxsBySessionId(sessionId).forEach(tx => {
    tx.resolve()
  })
}

export function RetryableNegative (context, data, wire) {
  const { sessionId, errorId } = data
  const error = context.getError(errorId) || new Error('Client error')
  context.getTxsBySessionId(sessionId).forEach(tx => {
    tx.reject(error)
  })
}

export function SessionBeginTransaction (context, data, wire) {
  const { sessionId, txMeta: metadata, timeout } = data
  const session = context.getSession(sessionId)
  const tx = session.beginTransaction({ metadata, timeout })
  const id = context.addTx(tx, sessionId)
  wire.writeResponse('Transaction', { id })
}

export function TransactionCommit (context, data, wire) {
  const { txId: id } = data
  const { tx } = context.getTx(id)
  tx.commit()
    .then(() => wire.writeResponse('Transaction', { id }))
    .catch(e => {
      console.log('got some err: ' + JSON.stringify(e))
      wire.writeError(e)
    })
}

export function TransactionRollback (context, data, wire) {
  const { txId: id } = data
  const { tx } = context.getTx(id)
  tx.rollback()
    .then(() => wire.writeResponse('Transaction', { id }))
    .catch(e => wire.writeError(e))
}

export function SessionLastBookmarks (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  const bookmarks = session.lastBookmark()
  wire.writeResponse('Bookmarks', { bookmarks })
}

export function SessionWriteTransaction (context, data, wire) {
  const { sessionId } = data
  const session = context.getSession(sessionId)
  session
    .writeTransaction(
      tx =>
        new Promise((resolve, reject) => {
          const id = context.addTx(tx, sessionId, resolve, reject)
          wire.writeResponse('RetryableTry', { id })
        })
    )
    .then(_ => wire.writeResponse('RetryableDone', null))
    .catch(error => wire.writeError(error))
}

export function StartTest (_, { testName }, wire) {
  shouldRunTest(testName, {
    onRun: () => wire.writeResponse('RunTest', null),
    onSkip: reason => wire.writeResponse('SkipTest', { reason })
  })
}

export function GetFeatures (_context, _params, wire) {
  wire.writeResponse('FeatureList', {
    features: [
      'Feature:Auth:Custom',
      'Feature:Auth:Kerberos',
      'AuthorizationExpiredTreatment',
      'ConfHint:connection.recv_timeout_seconds'
    ]
  })
}

export function VerifyConnectivity (context, { driverId }, wire) {
  const driver = context.getDriver(driverId)
  driver
    .verifyConnectivity()
    .then(() => wire.writeResponse('Driver', { id: driverId }))
    .catch(error => wire.writeError(error))
}

export function CheckMultiDBSupport (context, { driverId }, wire) {
  const driver = context.getDriver(driverId)
  driver
    .supportsMultiDb()
    .then(available =>
      wire.writeResponse('MultiDBSupport', { id: driverId, available })
    )
    .catch(error => wire.writeError(error))
}

export function ResolverResolutionCompleted (
  context,
  { requestId, addresses },
  wire
) {
  const request = context.getResolverRequest(requestId)
  request.resolve(addresses)
}

export function GetRoutingTable (context, { driverId, database }, wire) {
  const serverAddressToString = serverAddress => serverAddress.asHostPort()
  const driver = context.getDriver(driverId)
  const routingTable =
    driver &&
    driver._getOrCreateConnectionProvider() &&
    driver._getOrCreateConnectionProvider()._routingTableRegistry &&
    driver._getOrCreateConnectionProvider()._routingTableRegistry.get(database)

  if (routingTable) {
    wire.writeResponse('RoutingTable', {
      database: routingTable.database,
      ttl: routingTable.ttl,
      readers: routingTable.readers.map(serverAddressToString),
      writers: routingTable.writers.map(serverAddressToString),
      routers: routingTable.routers.map(serverAddressToString)
    })
  } else {
    wire.writeError('Could not find routing table')
  }
}
<|endoftext|>


<commit_before>
/* global enchant, Class, HEIGHT, WIDTH, SPEED, TRANSITION, PLAYER_LIVES */

//init enchant.js
WIDTH = 2048;
HEIGHT = 1152;
SPEED = 800;
TRANSITION = 10;

PLAYER_LIVES = 3;

var FALLING_OBJECTS = [
];

// enchant.ENV.USE_ANIMATION  = false;
enchant.ENV.TOUCH_ENABLED  = false;
enchant.ENV.RETINA_DISPLAY = false;

enchant();

var Marker = Class.create(enchant.Node, { });

////////////////////////////

function Pool () {
  this.pools = [];
}
Pool.prototype.get = function() {
  if ( this.pools.length > 0 ) {
    return this.pools.pop();
  }
  // console.log('pool ran out!');
  return null;
};
Pool.prototype.add = function(v) {
  this.pools.push(v);
};

////////////////////////////

function spritefromAsset (asset) {
    var sprite = new enchant.Sprite(asset.width, asset.height);
    sprite.image = asset;
    sprite.touchEnabled = false;
    sprite.disableCollection();
    return sprite;
}

//////////////////  SCENES  /////////////////////

var SceneInfos = Class.create(enchant.Group, {
    initialize: function (game, lives, onLoose) {

      var self = this;

      enchant.Group.call(this);

      this.lives = lives;
      this.onLoose = onLoose;

      this.x = 100;
      this.y = 100;
      this.width = 3 * 70;
      this.height = 70;
      this.game = game;

      this.hearts = [];

      this.coeurgris = this.game.assets['distimg/coeurgris.png'];
      this.coeurrouge = game.assets['distimg/coeurrouge.png'];
      this.coeurrouge.coeurrouge = true;

      for (var i = 0; i < 3; i++) {
        this.hearts[i] = new enchant.Sprite(this.coeurgris.width, this.coeurgris.height);
        this.hearts[i].image = this.coeurrouge;
        this.hearts[i].y = 10;
        this.hearts[i].x = (3-i) * 70;
        this.hearts[i].width = 62;
        this.hearts[i].height = 53;
        this.hearts[i].coeurrouge = true;
        this.addChild(this.hearts[i]);
      }
    },
    lostALife: function () {
      for (var i = 0; i < 3; i++) {
        if (this.hearts[i].image.coeurrouge) {
          this.hearts[i].image = this.coeurgris;
          this.lives--;
          break;
        }
      }
      if (this.lives <= 0) {
        setTimeout(this.onLoose, 20);
      }
    }
});

var SceneOne = Class.create(enchant.Group, {
  initialize: function (game) {
    var self = this;

    this.game = game;
    enchant.Group.call(this);

    this.width   = WIDTH;
    this.height  = HEIGHT;

    // Preload backgounds
    var backgrounds = [];
    for (var i = 0; i < 3; i++) {
      backgrounds[i] = spritefromAsset(game.assets['distimg/BG'+(i+1)+'.jpg']);
      backgrounds[i].width = WIDTH;
      this.addChild(backgrounds[i]);
    }

    // Loop backgrounds
    var currentBG = 0;
    this.tl
      .delay(20)
      .then(function(){
        if (! self.lightsAreOff()) {
          backgrounds[currentBG].tl.fadeOut(10);
          currentBG = (currentBG + 1) % 3;
          backgrounds[currentBG].tl.fadeIn(10);
        }
      })
      .loop();

    // Load characters
    var character;
    this.characters = [];

    // Claudette
    character = spritefromAsset(game.assets['distimg/claudette1.png']);
    character.type = 'chick';
    character.x = WIDTH / 6;
    this.characters.push(character);
    this.addChild(character);

    // Claudette
    character = spritefromAsset(game.assets['distimg/claudette2.png']);
    character.type = 'chick';
    character.x = WIDTH / 6 + this.characters[0].width;
    this.characters.push(character);
    this.addChild(character);

    // Inspecteur
    character = spritefromAsset(game.assets['distimg/jeanmichel.png']);
    character.type = 'agent';
    character.x = WIDTH / 6 + this.characters[0].width + this.characters[1].width;
    this.characters.push(character);
    this.addChild(character);

    // Preload spots
    var spots = this.spots = [];
    for (var i = 0; i < 3; i++) {
      spots[i] = spritefromAsset(game.assets['distimg/spot'+(i+1)+'.png']);
      spots[i].width = WIDTH;
      this.addChild(spots[i]);
    }

    // Load bath
    var theBath = spritefromAsset(game.assets['distimg/bain.png']);
    theBath.width = WIDTH;
    this.addChild(theBath);

    // Loop spots
    var currentSpot = 0;
    this.tl
      .delay(20)
      .then(function(){
        if (! self.lightsAreOff()) {
          spots[currentSpot].tl.fadeOut(10);
          currentSpot = (currentSpot + 1) % 3;
          spots[currentSpot].tl.fadeIn(10);
        }
      })
      .loop();

    // The "lights off" layer
    var lightsOff = this.lightsOff = new enchant.Sprite(WIDTH, HEIGHT);
    lightsOff.backgroundColor = 'black';
    lightsOff.opacity = 0;
    this.addChild(lightsOff);

    // Load switch
    var theSwitch = this.theSwitch = spritefromAsset(game.assets['distimg/interrupteur.png']);
    theSwitch.x = WIDTH * 0.9;
    theSwitch.y = HEIGHT * 0.3;
    theSwitch.touchEnabled = true;
    this.addChild(theSwitch);

    // Plug switch
    theSwitch.addEventListener('touchstart', function() {
      self.toggleLights();
    });

    // Load marker
    var marker = this.marker = spritefromAsset(game.assets['distimg/marker.png']);
    this.placeMarker(0);
    this.addChild(marker);

    // Load throwables
    var object = spritefromAsset(game.assets['distimg/interrupteur.png']);
    object.name = 'heart';
    object.x = object.original_x = WIDTH * 0.8;
    object.y = object.original_y = HEIGHT * 2;
    object.touchEnabled = true;
    this.addChild(object);

    setInterval(function () {
      self.game.throwObject(object, { name:'chick', x:WIDTH/2 * Math.random(), y:-100 });
    }, 1000);

  },

  marked: null,
  markedIdx: 0,
  moveLeft: function () {
    this.placeMarker( Math.max(this.markedIdx - 1, 0) );
  },
  moveRight: function () {
    this.placeMarker( Math.min(this.markedIdx + 1, this.characters.length - 1) );
  },
  placeMarker: function (characterIdx) {
    this.markedIdx = characterIdx;
    this.marked = this.characters[this.markedIdx];
    this.marker.tl
      .moveTo(this.characters[this.markedIdx].x + this.characters[this.markedIdx].width / 2, 0, 10);
  },

  lightsAreOff: function () {
    return this.lightsOff.opacity > 0.5;
  },

  toggleLights: function (event) {
    this.lightsOff.opacity = this.lightsAreOff() ? 0 : 0.98;
    this.theSwitch.image = this.lightsAreOff() ? this.game.assets['distimg/interrupteur.png'] : this.game.assets['distimg/interrupteur2.png'];

    for (var i = 0; i < 3; i++) {
        this.spots[i].opacity = this.lightsAreOff() ? 0 : 1;
    }

  },

});
SceneOne.preload = [
];
for (var i = 0; i < 6; i++) { SceneOne.preload.push('distimg/imm' + (i+1) + '-j-fs8.png'); }

(function () {

var game;

var settings = {
  player: {
    lives: 3,
    sprite: 'distimg/claude.png',
  },
  levels: [
      SceneOne,
  ]
};

// Character
function addCharacter (game, scene, asset) {
  var object = new enchant.Sprite(asset.width, asset.height);
  object.image = asset;
  object.width = 20;
  object.x = WIDTH / 2 - object.width / 2;
  object.y = HEIGHT / 2 - object.height;
  object.touchEnabled = false;
  object.disableCollection();
  scene.addChild(object);
  return object;
}

// Table
function addTable (game, scene, objects) {
  var asset =  game.assets['table'];
  var object = new enchant.Sprite(asset.width, asset.height);
  object.image = asset;
  object.width = 20;
  object.x = WIDTH / 2 - object.width / 2;
  object.y = HEIGHT / 2 - object.height;
  object.touchEnabled = false;
  object.disableCollection();
  scene.addChild(object);
  return object;
}

/**
 * Main
 */
var Game = function () {

  var self = this;

  game = this.game = new enchant.Core(WIDTH, HEIGHT); //screen res
  game.fps = 30;

  var preload = [
    'distimg/BG1.jpg',
    'distimg/BG2.jpg',
    'distimg/BG3.jpg',
    'distimg/spot1.png',
    'distimg/spot2.png',
    'distimg/spot3.png',
    'distimg/bain.png',
    'distimg/interrupteur.png',
    'distimg/interrupteur2.png',
    'distimg/coeurgris.png',
    'distimg/coeurrouge.png',
    'distimg/jeanmichel.png',
    'distimg/claudette1.png',
    'distimg/claudette2.png',
    'distimg/marker.png'
  ];

  game.preload(preload); //preload assets png, wav etc

  game.onload = function () {
    self.loadLevel(0);
  };

  game.rootScene.addEventListener('keydown', function(event) {
    debugger;
  });

  game.rootScene.addEventListener('touchstart', function(event) {
    if (event.localX < WIDTH / 2)
      self.scene.moveLeft();
    else
      self.scene.moveRight();
  });

  function changevisibility() {
    if (document.hidden === false || document.webkitHidden === false) {
      game.resume();
    } else {
      self.game.pause();
    }
  }
  document.addEventListener('visibilitychange', changevisibility, false);
  document.addEventListener('webkitvisibilitychange', changevisibility, false);

  game.throwObject = function(object, to) {
    object.tl
      .moveTo(self.scene.marker.x - self.scene.marker.width / 2, HEIGHT * 0.1, 10)
      .then(function(){

          var target = self.scene.marked;

          // heart on chick
          if (object.name == 'heart' && target.type == 'chick') {

          }

          // money on chick
          if (object.name == 'money' && target.type == 'chick') {

          }

          object.x = object.original_x;
          object.y = object.original_y;
      });
  };

  game.start();
};

Game.prototype.loadLevel = function(levelIndex) {

  if (this.scene) {
    game.rootScene.removeChild(this.scene);
  }

  this.scene = new settings.levels[levelIndex](game);
  game.rootScene.addChild(this.scene);

  // game.infos = new SceneInfos(game, PLAYER_LIVES, this.loose.bind(this));
  // game.rootScene.addChild(game.infos);
};

Game.prototype.loose = function() {
  this.game.pause();
  setTimeout(function(){
    alert('You loose');
  }, 10);
};

window.bbqggj = new Game();

})();

<commit_msg>shuffled targets<commit_after>
/* global enchant, Class, HEIGHT, WIDTH, SPEED, TRANSITION, PLAYER_LIVES */

//init enchant.js
WIDTH = 2048;
HEIGHT = 1152;
SPEED = 800;
TRANSITION = 10;

PLAYER_LIVES = 3;

var FALLING_OBJECTS = [
];

// enchant.ENV.USE_ANIMATION  = false;
enchant.ENV.TOUCH_ENABLED  = false;
enchant.ENV.RETINA_DISPLAY = false;

enchant();

var Marker = Class.create(enchant.Node, { });

function shuffle(o){ //v1.0
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

////////////////////////////

function Pool () {
  this.pools = [];
}
Pool.prototype.get = function() {
  if ( this.pools.length > 0 ) {
    return this.pools.pop();
  }
  // console.log('pool ran out!');
  return null;
};
Pool.prototype.add = function(v) {
  this.pools.push(v);
};

////////////////////////////

function spritefromAsset (asset) {
    var sprite = new enchant.Sprite(asset.width, asset.height);
    sprite.image = asset;
    sprite.touchEnabled = false;
    sprite.disableCollection();
    return sprite;
}

//////////////////  SCENES  /////////////////////

var SceneInfos = Class.create(enchant.Group, {
    initialize: function (game, lives, onLoose) {

      var self = this;

      enchant.Group.call(this);

      this.lives = lives;
      this.onLoose = onLoose;

      this.x = 100;
      this.y = 100;
      this.width = 3 * 70;
      this.height = 70;
      this.game = game;

      this.hearts = [];

      this.coeurgris = this.game.assets['distimg/coeurgris.png'];
      this.coeurrouge = game.assets['distimg/coeurrouge.png'];
      this.coeurrouge.coeurrouge = true;

      for (var i = 0; i < 3; i++) {
        this.hearts[i] = new enchant.Sprite(this.coeurgris.width, this.coeurgris.height);
        this.hearts[i].image = this.coeurrouge;
        this.hearts[i].y = 10;
        this.hearts[i].x = (3-i) * 70;
        this.hearts[i].width = 62;
        this.hearts[i].height = 53;
        this.hearts[i].coeurrouge = true;
        this.addChild(this.hearts[i]);
      }
    },
    lostALife: function () {
      for (var i = 0; i < 3; i++) {
        if (this.hearts[i].image.coeurrouge) {
          this.hearts[i].image = this.coeurgris;
          this.lives--;
          break;
        }
      }
      if (this.lives <= 0) {
        setTimeout(this.onLoose, 20);
      }
    }
});

var SceneOne = Class.create(enchant.Group, {
  initialize: function (game) {
    var self = this;

    this.game = game;
    enchant.Group.call(this);

    this.width   = WIDTH;
    this.height  = HEIGHT;

    // Preload backgounds
    var backgrounds = [];
    for (var i = 0; i < 3; i++) {
      backgrounds[i] = spritefromAsset(game.assets['distimg/BG'+(i+1)+'.jpg']);
      backgrounds[i].width = WIDTH;
      this.addChild(backgrounds[i]);
    }

    // Loop backgrounds
    var currentBG = 0;
    this.tl
      .delay(20)
      .then(function(){
        if (! self.lightsAreOff()) {
          backgrounds[currentBG].tl.fadeOut(10);
          currentBG = (currentBG + 1) % 3;
          backgrounds[currentBG].tl.fadeIn(10);
        }
      })
      .loop();

    // Load characters
    var character;
    this.characters = [];

    // Claudette
    character = spritefromAsset(game.assets['distimg/claudette1.png']);
    character.type = 'chick';
    this.characters.push(character);
    this.addChild(character);

    // Claudette
    character = spritefromAsset(game.assets['distimg/claudette2.png']);
    character.type = 'chick';
    this.characters.push(character);
    this.addChild(character);

    // Inspecteur
    character = spritefromAsset(game.assets['distimg/jeanmichel.png']);
    character.type = 'agent';
    this.characters.push(character);
    this.addChild(character);

    this.shuffleCharacters();

    // Anim characters
    // Loop spots
    this.tl.setTimeBased();
    this.tl
      .then(function(){
        if (! self.lightsAreOff()) {
          for (var i = 0; i < self.characters.length; i++) {
            self.characters[i].tl.setTimeBased();
            self.characters[i].tl.moveY(self.characters[i].y + (Math.random() -1) * Math.sin(window.performance.now()) * 10, 10);
          };
          spots[currentSpot].tl.fadeOut(10);
          currentSpot = (currentSpot + 1) % 3;
          spots[currentSpot].tl.fadeIn(10);
        }
      })
      .loop();

    // Preload spots
    var spots = this.spots = [];
    for (var i = 0; i < 3; i++) {
      spots[i] = spritefromAsset(game.assets['distimg/spot'+(i+1)+'.png']);
      spots[i].width = WIDTH;
      this.addChild(spots[i]);
    }

    // Load bath
    var theBath = spritefromAsset(game.assets['distimg/bain.png']);
    theBath.width = WIDTH;
    this.addChild(theBath);

    // Loop spots
    var currentSpot = 0;
    this.tl
      .delay(20)
      .then(function(){
        if (! self.lightsAreOff()) {
          spots[currentSpot].tl.fadeOut(10);
          currentSpot = (currentSpot + 1) % 3;
          spots[currentSpot].tl.fadeIn(10);
        }
      })
      .loop();

    // The "lights off" layer
    var lightsOff = this.lightsOff = new enchant.Sprite(WIDTH, HEIGHT);
    lightsOff.backgroundColor = 'black';
    lightsOff.opacity = 0;
    this.addChild(lightsOff);

    // Load switch
    var theSwitch = this.theSwitch = spritefromAsset(game.assets['distimg/interrupteur.png']);
    theSwitch.x = WIDTH * 0.9;
    theSwitch.y = HEIGHT * 0.84;
    theSwitch.touchEnabled = true;
    this.addChild(theSwitch);

    // Plug switch
    theSwitch.addEventListener('touchstart', function() {
      self.shuffleCharacters();
      self.toggleLights();
    });

    // Load marker
    var marker = this.marker = spritefromAsset(game.assets['distimg/marker.png']);
    this.placeMarker(0);
    this.addChild(marker);

    // Load throwables
    var object = spritefromAsset(game.assets['distimg/interrupteur.png']);
    object.name = 'heart';
    object.x = object.original_x = WIDTH * 0.45;
    object.y = object.original_y = HEIGHT * 0.75;
    object.touchEnabled = true;
    this.addChild(object);

    setInterval(function () {
      self.game.throwObject(object, { name:'chick', x:WIDTH/2 * Math.random(), y:-100 });
    }, 1000);

  },

  marked: null,
  markedIdx: 0,
  moveLeft: function () {
    this.placeMarker( Math.max(this.markedIdx - 1, 0) );
  },
  moveRight: function () {
    this.placeMarker( Math.min(this.markedIdx + 1, this.characters.length - 1) );
  },
  placeMarker: function (characterIdx) {
    this.markedIdx = characterIdx;
    this.marked = this.characters[this.markedIdx];
    this.marker.tl
      .moveTo(this.characters[this.markedIdx].x + this.characters[this.markedIdx].width / 2, 0, 10);
  },

  shuffleCharacters: function () {
    shuffle(this.characters);
    this.characters[0].x = WIDTH / 6;
    this.characters[1].x = WIDTH / 6 + this.characters[0].width / 2;
    this.characters[2].x = WIDTH / 6 + this.characters[0].width / 2 + this.characters[1].width / 2;
  },

  lightsAreOff: function () {
    return this.lightsOff.opacity > 0.5;
  },

  toggleLights: function (event) {
    this.lightsOff.opacity = this.lightsAreOff() ? 0 : 0.98;
    this.theSwitch.image = this.lightsAreOff() ? this.game.assets['distimg/interrupteur.png'] : this.game.assets['distimg/interrupteur2.png'];

    for (var i = 0; i < 3; i++) {
        this.spots[i].opacity = this.lightsAreOff() ? 0 : 1;
    }

  },

});
SceneOne.preload = [
];
for (var i = 0; i < 6; i++) { SceneOne.preload.push('distimg/imm' + (i+1) + '-j-fs8.png'); }

(function () {

var game;

var settings = {
  player: {
    lives: 3,
    sprite: 'distimg/claude.png',
  },
  levels: [
      SceneOne,
  ]
};

// Character
function addCharacter (game, scene, asset) {
  var object = new enchant.Sprite(asset.width, asset.height);
  object.image = asset;
  object.width = 20;
  object.x = WIDTH / 2 - object.width / 2;
  object.y = HEIGHT / 2 - object.height;
  object.touchEnabled = false;
  object.disableCollection();
  scene.addChild(object);
  return object;
}

// Table
function addTable (game, scene, objects) {
  var asset =  game.assets['table'];
  var object = new enchant.Sprite(asset.width, asset.height);
  object.image = asset;
  object.width = 20;
  object.x = WIDTH / 2 - object.width / 2;
  object.y = HEIGHT / 2 - object.height;
  object.touchEnabled = false;
  object.disableCollection();
  scene.addChild(object);
  return object;
}

/**
 * Main
 */
var Game = function () {

  var self = this;

  game = this.game = new enchant.Core(WIDTH, HEIGHT); //screen res
  game.fps = 30;

  var preload = [
    'distimg/BG1.jpg',
    'distimg/BG2.jpg',
    'distimg/BG3.jpg',
    'distimg/spot1.png',
    'distimg/spot2.png',
    'distimg/spot3.png',
    'distimg/bain.png',
    'distimg/interrupteur.png',
    'distimg/interrupteur2.png',
    'distimg/coeurgris.png',
    'distimg/coeurrouge.png',
    'distimg/jeanmichel.png',
    'distimg/claudette1.png',
    'distimg/claudette2.png',
    'distimg/marker.png'
  ];

  game.preload(preload); //preload assets png, wav etc

  game.onload = function () {
    self.loadLevel(0);
  };

  game.rootScene.addEventListener('keydown', function(event) {
    debugger;
  });

  game.rootScene.addEventListener('touchstart', function(event) {
    if (event.localX < WIDTH / 2)
      self.scene.moveLeft();
    else
      self.scene.moveRight();
  });

  function changevisibility() {
    if (document.hidden === false || document.webkitHidden === false) {
      game.resume();
    } else {
      self.game.pause();
    }
  }
  document.addEventListener('visibilitychange', changevisibility, false);
  document.addEventListener('webkitvisibilitychange', changevisibility, false);

  game.throwObject = function(object, to) {
    object.tl
      .moveTo(self.scene.marker.x - self.scene.marker.width / 2, HEIGHT * 0.1, 10)
      .then(function(){

          var target = self.scene.marked;

          // heart on chick
          if (object.name == 'heart' && target.type == 'chick') {

          }

          // money on chick
          if (object.name == 'money' && target.type == 'chick') {

          }

          object.x = object.original_x;
          object.y = object.original_y;
      });
  };

  game.start();
};

Game.prototype.loadLevel = function(levelIndex) {

  if (this.scene) {
    game.rootScene.removeChild(this.scene);
  }

  this.scene = new settings.levels[levelIndex](game);
  game.rootScene.addChild(this.scene);

  // game.infos = new SceneInfos(game, PLAYER_LIVES, this.loose.bind(this));
  // game.rootScene.addChild(game.infos);
};

Game.prototype.loose = function() {
  this.game.pause();
  setTimeout(function(){
    alert('You loose');
  }, 10);
};

window.bbqggj = new Game();

})();

<|endoftext|>