<commit_before>
import fetch from 'isomorphic-fetch';
import moment from 'moment';

import * as types from '../constants/ActionTypes';
import Config from '../config';
import Mixins from '../mixins/global';
import { expiredToken } from './account';
import { getOneSurvey, postSurvey, receiveQuestionsFailure } from './questions';

export function requestSurveysFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.RECEIVE_SURVEYS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveSurveysSuccess(data) {
    return {
        type: types.RECEIVE_SURVEYS_SUCCESS,
        surveys: data
    };
}

export function getSurveys() {
    return (dispatch, getState) => {
        dispatch({ type: types.REMOVE_SELECTED_SURVEYS });
        dispatch({ type: types.REQUEST_SURVEYS_LIST });
        const { account, selectedUser, token } = getState();
        // fetch selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;

        return fetch(`${Config.baseURL}/api/v1/mgnt/surveys/${accountid}`, {
            method: 'GET',
            credentials: 'same-origin',
            headers: {
                authorization: token
            }
        })
        .then(response => response.json())
        .then(data => {
            const surveys = data.surveys.sort((a, b) => {
                if (a.datetime < b.datetime) return 1;
                if (a.datetime > b.datetime) return -1;
                return 0;
            });
            dispatch(receiveSurveysSuccess(surveys));
        })
        .catch(err => dispatch(requestSurveysFailure(err)));
    };
}

export function toggleSelectedSurveys(data) {
    return (dispatch, getState) => {
        if (getState().selectedSurveys === data) {
            dispatch({
                type: types.REMOVE_SELECTED_SURVEYS
            });
        } else {
            dispatch({
                type: types.ADD_SELECTED_SURVEYS,
                selectedSurveys: data
            });
        }
    };
}

export function requestDeleteSurveysFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.RECEIVE_DELETE_SURVEYS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveDeleteSurveysSuccess() {
    return {
        type: types.RECEIVE_DELETE_SURVEYS_SUCCESS
    };
}

export function deleteSurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_DELETE_SURVEYS });
        const { account, selectedUser, selectedSurveys, token } = getState();
        // delete selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/surveys/${accountid}/${selectedSurveys}`, {
                method: 'DELETE',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(() => {
                dispatch(receiveDeleteSurveysSuccess());
                dispatch(toggleSelectedSurveys(selectedSurveys));
                dispatch(getSurveys());
            })
            .catch(err => dispatch(requestDeleteSurveysFailure(err)));
    };
}

export function receiveReportSuccess() {
    return {
        type: types.RECEIVE_REPORT_SUCCESS
    };
}

export function receiveReportFailure(err) {
    return {
        type: types.RECEIVE_REPORT_FAILURE,
        errorMsg: err
    };
}

export function handleReportHeader(survey, privacy) {
    const header = ['Client ID'];
    survey.forEach((que) => {
        header.push(que.label);
        switch (que.type) {
        case 'checkbox':
            for (const opt of que.data) {
                header.push(opt.label);
                if (opt.hasOwnProperty('input')) {
                    header.push(opt.input);
                }
            }
            break;
        case 'radio':
            for (const opt of que.data) {
                if (opt.hasOwnProperty('input')) {
                    header.push(opt.input);
                    break;
                }
            }
            break;
        case 'rating':
            if (que.hasOwnProperty('input')) {
                header.push(que.input);
            }
            break;
        case 'text':
        case 'textarea':
        default:
        }
    });
    if (privacy) header.push('Privacy email');
    header.push('Feedback time');

    return [header];
}

export function handleReportContent(survey, privacy, feedbackAllData) {
    const content = [];
    for (const feed of feedbackAllData) {
        const data = feed.feedback;
        const body = [`${feed.clientid}${String.fromCharCode(8)}`];
        survey.forEach((que, idx) => {
            const num = idx + 1;
            const feedbackQue = data[`Q${num}`];
            if (feedbackQue) {
                let i = 0;
                const chk = [];
                switch (que.type) {
                case 'checkbox':
                    que.data.forEach((opt, optIdx) => {
                        chk.push(feedbackQue.data[optIdx].label);
                        if (opt.hasOwnProperty('input')) {
                            chk.push(feedbackQue.data[optIdx].input);
                        }
                        if (opt.label === feedbackQue.data[optIdx].label) i ++;
                    });
                    body.push(i.toString());
                    body.push(...chk);
                    break;
                case 'radio':
                    body.push(feedbackQue.data[0].label);
                    for (const opt of que.data) {
                        if (opt.hasOwnProperty('input')) {
                            body.push(feedbackQue.data[0].hasOwnProperty('input') ?
                                feedbackQue.data[0].input : '');
                            break;
                        }
                    }
                    break;
                case 'rating':
                    body.push(feedbackQue.data[0].label);
                    if (que.hasOwnProperty('input')) {
                        body.push(feedbackQue.data[0].hasOwnProperty('input') ?
                            feedbackQue.data[0].input : '');
                    }
                    break;
                case 'text':
                case 'textarea':
                    body.push(feedbackQue.data[0].input || '');
                    break;
                default:
                }
            } else {
                // if no feedback in this one question
                body.push('');
                switch (que.type) {
                case 'checkbox':
                    que.data.forEach((opt) => {
                        body.push('');
                        if (opt.hasOwnProperty('input')) {
                            body.push('');
                        }
                    });
                    break;
                case 'radio':
                    for (const opt of que.data) {
                        if (opt.hasOwnProperty('input')) {
                            body.push('');
                            break;
                        }
                    }
                    break;
                case 'rating':
                    if (que.hasOwnProperty('input')) {
                        body.push('');
                    }
                    break;
                case 'text':
                case 'textarea':
                default:
                }
            }
        });
        if (privacy) {
            body.push(
                data.hasOwnProperty('thankyou') ?
                data.thankyou.privacy.input :
                ''
            );
        }
        body.push(moment(feed.datetime).format('LLL'));

        content.push(body);
    }

    return content;
}

export function exportSurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_REPORT });
        const { account, selectedUser, selectedSurveys, token } = getState();
        // export selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/report/${accountid}/${selectedSurveys}`, {
                method: 'GET',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(report => {
                const survey = report.survey.content;
                const feedback = report.data;
                const privacy = report.survey.thankyou.privacy.hasOwnProperty('label');

                let allSurvey = [];
                survey.forEach((page) => {
                    allSurvey = [...allSurvey, ...page.question];
                });

                Mixins.exportCSV(
                    `[Qustom]${report.subject}_${moment(Date.now()).format('YYYYMMDDHHmmss')}`,
                    'v1',
                    handleReportHeader(allSurvey, privacy),
                    handleReportContent(allSurvey, privacy, feedback)
                );
                dispatch(receiveReportSuccess());
            })
            .catch(err => dispatch(receiveReportFailure(err)));
    };
}

export function postCopiedSurveySuccess() {
    return {
        type: types.POST_COPIEDSURVEY_SUCCESS
    };
}

export function postCopiedSurveyFailure(err) {
    return {
        type: types.POST_COPIEDSURVEY_FAILURE,
        errorMsg: err
    };
}

export function postCopiedSurvey(questions) {
    return (dispatch, getState) => {
        const { account, token } = getState();
        const postData = {
            subject: questions.subject,
            survey: questions.survey,
            l10n: questions.l10n || {}
        };

        return postSurvey(account.accountid, postData, token)
            .then(response => response.json())
            .then(data => {
                if (data.surveyid) {
                    dispatch(postCopiedSurveySuccess());
                    dispatch(getSurveys());
                } else {
                    dispatch(postCopiedSurveyFailure(data));
                }
            })
            .catch(err => dispatch(postCopiedSurveyFailure(err)));
    };
}

export function copySurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_COPY_SURVEY });
        const { account, selectedSurveys } = getState();

        return getOneSurvey(account.accountid, selectedSurveys)
            .then(response => response.json())
            .then(data => {
                if (data.surveyid) {
                    dispatch(postCopiedSurvey(data));
                } else {
                    dispatch(receiveQuestionsFailure(data));
                }
            })
            .catch(err => dispatch(receiveQuestionsFailure(err)));
    };
}

export function requestDeleteAllFeedbacksFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.DELETE_ALLFEEDBACKS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveDeleteAllFeedbacksSuccess() {
    return {
        type: types.DELETE_ALLFEEDBACKS_SUCCESS
    };
}

export function deleteAllFeedbacks() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_DELETE_ALLFEEDBACKS });
        const { selectedSurveys, token } = getState();
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/feedbacks/${selectedSurveys}`, {
                method: 'DELETE',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(() => {
                dispatch(receiveDeleteAllFeedbacksSuccess());
                dispatch(getSurveys());
            })
            .catch(err => dispatch(requestDeleteAllFeedbacksFailure(err)));
    };
}
<commit_msg>Export CSV for l10n format<commit_after>
import fetch from 'isomorphic-fetch';
import moment from 'moment';

import * as types from '../constants/ActionTypes';
import Config from '../config';
import Mixins from '../mixins/global';
import { expiredToken } from './account';
import { getOneSurvey, postSurvey, receiveQuestionsFailure } from './questions';

export function requestSurveysFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.RECEIVE_SURVEYS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveSurveysSuccess(data) {
    return {
        type: types.RECEIVE_SURVEYS_SUCCESS,
        surveys: data
    };
}

export function getSurveys() {
    return (dispatch, getState) => {
        dispatch({ type: types.REMOVE_SELECTED_SURVEYS });
        dispatch({ type: types.REQUEST_SURVEYS_LIST });
        const { account, selectedUser, token } = getState();
        // fetch selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;

        return fetch(`${Config.baseURL}/api/v1/mgnt/surveys/${accountid}`, {
            method: 'GET',
            credentials: 'same-origin',
            headers: {
                authorization: token
            }
        })
        .then(response => response.json())
        .then(data => {
            const surveys = data.surveys.sort((a, b) => {
                if (a.datetime < b.datetime) return 1;
                if (a.datetime > b.datetime) return -1;
                return 0;
            });
            dispatch(receiveSurveysSuccess(surveys));
        })
        .catch(err => dispatch(requestSurveysFailure(err)));
    };
}

export function toggleSelectedSurveys(data) {
    return (dispatch, getState) => {
        if (getState().selectedSurveys === data) {
            dispatch({
                type: types.REMOVE_SELECTED_SURVEYS
            });
        } else {
            dispatch({
                type: types.ADD_SELECTED_SURVEYS,
                selectedSurveys: data
            });
        }
    };
}

export function requestDeleteSurveysFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.RECEIVE_DELETE_SURVEYS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveDeleteSurveysSuccess() {
    return {
        type: types.RECEIVE_DELETE_SURVEYS_SUCCESS
    };
}

export function deleteSurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_DELETE_SURVEYS });
        const { account, selectedUser, selectedSurveys, token } = getState();
        // delete selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/surveys/${accountid}/${selectedSurveys}`, {
                method: 'DELETE',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(() => {
                dispatch(receiveDeleteSurveysSuccess());
                dispatch(toggleSelectedSurveys(selectedSurveys));
                dispatch(getSurveys());
            })
            .catch(err => dispatch(requestDeleteSurveysFailure(err)));
    };
}

export function receiveReportSuccess() {
    return {
        type: types.RECEIVE_REPORT_SUCCESS
    };
}

export function receiveReportFailure(err) {
    return {
        type: types.RECEIVE_REPORT_FAILURE,
        errorMsg: err
    };
}

function getL10n(l10n, key) {
    return l10n[key] || key;
}

export function handleReportHeader(survey, privacy, l10n) {
    const header = ['Client ID', 'Locale'];
    survey.forEach((que, idx) => {
        header.push(`Q${idx + 1}_${getL10n(l10n, que.label)}`);
        switch (que.type) {
        case 'checkbox':
            for (const opt of que.data) {
                header.push(getL10n(l10n, opt.label));
                if (opt.hasOwnProperty('input')) {
                    header.push(getL10n(l10n, opt.input));
                }
            }
            break;
        case 'radio':
            for (const opt of que.data) {
                if (opt.hasOwnProperty('input')) {
                    header.push(getL10n(l10n, opt.input));
                    break;
                }
            }
            break;
        case 'rating':
            if (que.hasOwnProperty('input')) {
                header.push(getL10n(l10n, que.input));
            }
            break;
        case 'text':
        case 'textarea':
        default:
        }
    });
    if (privacy) header.push('Privacy email');
    header.push('Feedback time');

    return [header];
}

export function handleReportContent(survey, privacy, feedbackAllData, l10n) {
    const content = [];
    for (const feed of feedbackAllData) {
        const data = feed.feedback;
        const body = [`${feed.clientid}${String.fromCharCode(8)}`, data.locale || ''];
        survey.forEach((que, idx) => {
            const num = idx + 1;
            const feedbackQue = data[`Q${num}`];
            if (feedbackQue) {
                let i = 0;
                const chk = [];
                switch (que.type) {
                case 'checkbox':
                    que.data.forEach((opt, optIdx) => {
                        if (feedbackQue.data[optIdx].label === ' ') {
                            // user didnot choose this option
                            chk.push(feedbackQue.data[optIdx].label);
                        } else {
                            chk.push(l10n[feedbackQue.data[optIdx].value] ||
                                feedbackQue.data[optIdx].label);
                            i ++;
                        }
                        if (opt.hasOwnProperty('input')) {
                            chk.push(feedbackQue.data[optIdx].input);
                        }
                    });
                    body.push(i.toString());
                    body.push(...chk);
                    break;
                case 'radio':
                    body.push(l10n[feedbackQue.data[0].value] ||
                        feedbackQue.data[0].label);
                    for (const opt of que.data) {
                        if (opt.hasOwnProperty('input')) {
                            body.push(feedbackQue.data[0].hasOwnProperty('input') ?
                                feedbackQue.data[0].input : '');
                            break;
                        }
                    }
                    break;
                case 'rating':
                    body.push(l10n[feedbackQue.data[0].value] ||
                        feedbackQue.data[0].label);
                    if (que.hasOwnProperty('input')) {
                        body.push(feedbackQue.data[0].hasOwnProperty('input') ?
                            feedbackQue.data[0].input : '');
                    }
                    break;
                case 'text':
                case 'textarea':
                    body.push(feedbackQue.data[0].input || '');
                    break;
                default:
                }
            } else {
                // if no feedback in this one question
                body.push('');
                switch (que.type) {
                case 'checkbox':
                    que.data.forEach((opt) => {
                        body.push('');
                        if (opt.hasOwnProperty('input')) {
                            body.push('');
                        }
                    });
                    break;
                case 'radio':
                    for (const opt of que.data) {
                        if (opt.hasOwnProperty('input')) {
                            body.push('');
                            break;
                        }
                    }
                    break;
                case 'rating':
                    if (que.hasOwnProperty('input')) {
                        body.push('');
                    }
                    break;
                case 'text':
                case 'textarea':
                default:
                }
            }
        });
        if (privacy) {
            body.push(
                data.hasOwnProperty('thankyou') ?
                data.thankyou.privacy.input :
                ''
            );
        }
        body.push(moment(feed.datetime).format('LLL'));

        content.push(body);
    }

    return content;
}

export function exportSurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_REPORT });
        const { account, selectedUser, selectedSurveys, token } = getState();
        // export selected user account or user's account
        const accountid = selectedUser.hasOwnProperty('accountid') ?
            selectedUser.accountid : account.accountid;
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/report/${accountid}/${selectedSurveys}`, {
                method: 'GET',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(report => {
                const survey = report.survey.content;
                const l10n = report.hasOwnProperty('l10n') ?
                    report.l10n[report.l10n.basic] : {};
                const feedback = report.data;
                const privacy = report.survey.thankyou.privacy.hasOwnProperty('label');

                let allSurvey = [];
                survey.forEach((page) => {
                    allSurvey = [...allSurvey, ...page.question];
                });

                Mixins.exportCSV(
                    `[Qustom]${report.subject}_${moment(Date.now()).format('YYYYMMDDHHmmss')}`,
                    'v2',
                    handleReportHeader(allSurvey, privacy, l10n),
                    handleReportContent(allSurvey, privacy, feedback, l10n)
                );
                dispatch(receiveReportSuccess());
            })
            .catch(err => dispatch(receiveReportFailure(err)));
    };
}

export function postCopiedSurveySuccess() {
    return {
        type: types.POST_COPIEDSURVEY_SUCCESS
    };
}

export function postCopiedSurveyFailure(err) {
    return {
        type: types.POST_COPIEDSURVEY_FAILURE,
        errorMsg: err
    };
}

export function postCopiedSurvey(questions) {
    return (dispatch, getState) => {
        const { account, token } = getState();
        const postData = {
            subject: questions.subject,
            survey: questions.survey,
            l10n: questions.l10n || {}
        };

        return postSurvey(account.accountid, postData, token)
            .then(response => response.json())
            .then(data => {
                if (data.surveyid) {
                    dispatch(postCopiedSurveySuccess());
                    dispatch(getSurveys());
                } else {
                    dispatch(postCopiedSurveyFailure(data));
                }
            })
            .catch(err => dispatch(postCopiedSurveyFailure(err)));
    };
}

export function copySurvey() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_COPY_SURVEY });
        const { account, selectedSurveys } = getState();

        return getOneSurvey(account.accountid, selectedSurveys)
            .then(response => response.json())
            .then(data => {
                if (data.surveyid) {
                    dispatch(postCopiedSurvey(data));
                } else {
                    dispatch(receiveQuestionsFailure(data));
                }
            })
            .catch(err => dispatch(receiveQuestionsFailure(err)));
    };
}

export function requestDeleteAllFeedbacksFailure(err) {
    return (dispatch) => {
        dispatch(expiredToken());
        dispatch({
            type: types.DELETE_ALLFEEDBACKS_FAILURE,
            errorMsg: err
        });
    };
}

export function receiveDeleteAllFeedbacksSuccess() {
    return {
        type: types.DELETE_ALLFEEDBACKS_SUCCESS
    };
}

export function deleteAllFeedbacks() {
    return (dispatch, getState) => {
        dispatch({ type: types.REQUEST_DELETE_ALLFEEDBACKS });
        const { selectedSurveys, token } = getState();
        return fetch(
            `${Config.baseURL}/api/v1/mgnt/feedbacks/${selectedSurveys}`, {
                method: 'DELETE',
                credentials: 'same-origin',
                headers: {
                    authorization: token
                }
            })
            .then(response => response.json())
            .then(() => {
                dispatch(receiveDeleteAllFeedbacksSuccess());
                dispatch(getSurveys());
            })
            .catch(err => dispatch(requestDeleteAllFeedbacksFailure(err)));
    };
}
<|endoftext|>


<commit_before>// ------------------------------------------------------------------------------
// Tetra.js
// Javascript MVC framework to build asynchronous webapp on client and server
//
// Author: Olivier Hory
// Contributors: Cormac Flynn, Yannick Croissant, Sylvain Faucherand
// ------------------------------------------------------------------------------
// Copyright (c) Viadeo/APVO Corp., Olivier Hory and other Tetra contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------

// Tetra namespace
// ------------------------------------------------------------------------------
var tns = {};

// Tetra structure
// ------------------------------------------------------------------------------
var tetra = (function() {

	// Settings:
	// - default environnement
	// - scripts and ajax calls default locations
	// ----------------------------------------
	var _conf = {
		env: 'jQuery',
		enableBootstrap: false,

		APPS_PATH : '/javascript/tetramvc/apps',
		GLOBAL_PATH : '/javascript/tetramvc',
		COMP_PATH : '/resource/comp',
		FETCH_URL : '/javascript/tetramvc/model/{name}/fetch.json',
		FETCH_METHOD : 'GET',
		SAVE_URL : '/javascript/tetramvc/model/{name}/save.json',
		SAVE_METHOD : 'POST',
		DEL_URL : '/javascript/tetramvc/model/{name}/delete.json',
		DEL_METHOD : 'DELETE',
		RESET_URL : '/javascript/tetramvc/model/{name}/reset.json',
		RESET_METHOD : 'PUT'
	};

	// Core modules of current instance
	// ----------------------------------------

	// Extendable objects
	var
		_, // "_" abstracted library
		_started = false,
		_tmp = {}, // all modules awaiting to be started
		_core = {}, // public modules
		_mod = {}, // internal modules
		_notImplem = function(mod, fct) {
			if(fct) {
				_mod.debug.log('module ' + mod + ' : function ' + fct + ' not implemented', 'all', 'error');
			} else {
				_mod.debug.log('module ' + mod + ' not implemented', 'all', 'error');
			}
		}
	;

	_mod.lib = _ = (typeof $ !== 'undefined') ? $ : null;
	_mod.dep = { // dependencies loader
		define: function() { _notImplem('dep', 'define'); },
		undef: function() { _notImplem('dep', 'undef'); },
		require: function() { _notImplem('dep', 'require'); }
	};
	_mod.orm = function() { // orm + ajax requester
		_notImplem('orm');
		return {
			type: 'interface',
			save: function() { _notImplem('orm', 'save'); },
			fetch: function() { _notImplem('orm', 'fetch'); },
			del: function() { _notImplem('orm', 'del'); },
			reset: function() { _notImplem('orm', 'reset'); },
			notify: function() { _notImplem('orm', 'notify'); }
		};
	};

	// PIPES for notifications
	// --------------------
	_mod.pipe = {
		page: {
			notify: function(message, data) {
				_mod.debug.log('page : ' + message, 'all', 'log', data);
				tetra.controller.notify(message, data);
				return this;
			}
		},

		appBuilder: function(target, scope) {
			if(target === 'view') {
				return {
					notify: function(message, data) {
						_mod.debug.log('app ' + scope + ' : ' + target + ' : ' + message, scope, 'log', data);
						tetra.view.notify(message, data, scope);
						return this;
					}
				};
			} else {
				return {
					notify: function(message, data) {
						_mod.debug.log('app ' + scope + ' : ' + target + ' : ' + message, scope, 'log', data);
						tetra.controller.notify(message, data, scope);
						return this;
					},
					exec: function() { // actionName, ctrlName (optional), data, cbk
						var args = [];
						for(var i in arguments) args.push(arguments[i]);
						args.push(scope);
						return tetra.controller.exec.apply(this, args);
					}
				};
			}
		}
	};

	// MVC interface
	// Implemented by native modules view, controller and model
	// --------------------
	_core.view = {
		register: function() { _notImplem('view', 'register'); },
		destroy: function() { _notImplem('view', 'destroy'); },
		notify: function() { _notImplem('view', 'notify'); },
		debug: {}
	};

	_core.controller = {
		register: function() { _notImplem('controller', 'register'); },
		destroy: function() { _notImplem('controller', 'destroy'); },
		notify: function() { _notImplem('controller', 'notify'); },
		modelNotify: function() { _notImplem('controller', 'modelNotify'); },
		exec: function() { _notImplem('controller', 'exec'); },
		debug: {}
	};

	_core.model = {
		register: function() { _notImplem('model', 'register'); },
		destroy: function() { _notImplem('model', 'destroy'); },
		debug: {}
	};

	// Logger + debugger
	// --------------------
	_core.debug = (function(){
		var
			_debugApp = {},
			debugLog = function(msg, scope, type, data) {
				scope = scope || 'all';
				if(typeof window.console !== 'undefined' && (_debugApp[scope] || (scope !== 'all' && _debugApp.all))){
					data = data || '';
					type = type || 'log';
					try {
						console[type](msg, data);
					} catch(e){}
				}
			}
		;

		_mod.debug = {
			log: function() {}
		};

		return {
			enable: function(scope) {
				scope = scope || 'all';
				_debugApp[scope] = true;

				// Allow access to underscore lib outside of tetra apps
				window._ = _;

				// Initialize internal and external debug objects
				_mod.debug = {
					log : debugLog
				};
				_core.debug = {
					enable : this.enable,

					man : function() {
						debugLog('Tetra.js ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
						debugLog('> debug enabled');
						debugLog('> man specs > tetra.debug.man()');
						debugLog('	# View specs			> tetra.debug.view.man()');
						debugLog('	# Controller specs		> tetra.debug.controller.man()');
						debugLog('	# Model specs			> tetra.debug.model.man()');
						debugLog('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');

						return true;
					},

					view: _core.view.debug,

					ctrl : _core.controller.debug,

					model: _core.model.debug
				};
			}
		};
	})();

	// Extend core functionality with new modules
	// --------------------
	_core.extend = (function() {

		var
			_add = function(name, module) {
				if(name == 'conf') {
					for(var attr in module) {
						_conf[attr] = module[attr];
					}
				} else {
					if(_started) {
						_addModule(name, module);
					} else {
						_tmp[name] = module;
						_mod.debug.log('Module "'+ name +'" defined');
					}
				}
			},

			_addModule = function(name, module) {
				if(typeof _core[name] !== 'undefined') {
					_core[name] = module(_conf, _mod, _);
					_mod.debug.log('Tetra module "'+ name +'" loaded');
				} else {
					if(name == 'lib') {
						_mod[name] = _ = module(_conf, _mod, _);
					} else {
						_mod[name] = module(_conf, _mod, _);
					}
					_mod.debug.log('Extension module "'+ name +'" loaded');
				}
			},

			_extend = function() {
				if(typeof arguments[0] !== 'undefined') {
					if(typeof arguments[0] === 'string') {
						_add(arguments[0], arguments[1]);
					} else {
						for(var name in arguments[0]) {
							_add(name, arguments[0][name]);
						}
					}
				}

				return _core;
			},

			_start = function() {
				_started = true;

				// start all module in the temporary stack
				for(var name in _tmp) {
					_addModule(name, _tmp[name]);
				}

				// empty the temporary module stack
				_tmp = [];
			}
		;

		// Start the core when all dependencies and conf are set
		// --------------------
		_core.start = _start;

		return _extend;
	})();

	// Initialization of core
	// --------------------

	// Public functions
	return _core;

})();

if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
	exports = module.exports = {};
	exports.tns = tns;
	exports.tetra = tetra;
}
<commit_msg>Fix templating system on IE8<commit_after>// ------------------------------------------------------------------------------
// Tetra.js
// Javascript MVC framework to build asynchronous webapp on client and server
//
// Author: Olivier Hory
// Contributors: Cormac Flynn, Yannick Croissant, Sylvain Faucherand
// ------------------------------------------------------------------------------
// Copyright (c) Viadeo/APVO Corp., Olivier Hory and other Tetra contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------

// Tetra namespace
// ------------------------------------------------------------------------------
var tns = {};

// Tetra structure
// ------------------------------------------------------------------------------
var tetra = (function() {

	// Settings:
	// - default environnement
	// - scripts and ajax calls default locations
	// ----------------------------------------
	var _conf = {
		env: 'jQuery',
		enableBootstrap: false,

		APPS_PATH : '/javascript/tetramvc/apps',
		GLOBAL_PATH : '/javascript/tetramvc',
		COMP_PATH : '/resource/comp',
		FETCH_URL : '/javascript/tetramvc/model/{name}/fetch.json',
		FETCH_METHOD : 'GET',
		SAVE_URL : '/javascript/tetramvc/model/{name}/save.json',
		SAVE_METHOD : 'POST',
		DEL_URL : '/javascript/tetramvc/model/{name}/delete.json',
		DEL_METHOD : 'DELETE',
		RESET_URL : '/javascript/tetramvc/model/{name}/reset.json',
		RESET_METHOD : 'PUT'
	};

	// Core modules of current instance
	// ----------------------------------------

	// Extendable objects
	var
		_, // "_" abstracted library
		_started = false,
		_tmp = {}, // all modules awaiting to be started
		_core = {}, // public modules
		_mod = {}, // internal modules
		_notImplem = function(mod, fct) {
			if(fct) {
				_mod.debug.log('module ' + mod + ' : function ' + fct + ' not implemented', 'all', 'error');
			} else {
				_mod.debug.log('module ' + mod + ' not implemented', 'all', 'error');
			}
		}
	;

	_mod.lib = _ = (typeof $ !== 'undefined') ? $ : null;
	_mod.dep = { // dependencies loader
		define: function() { _notImplem('dep', 'define'); },
		undef: function() { _notImplem('dep', 'undef'); },
		require: function() { _notImplem('dep', 'require'); }
	};
	_mod.orm = function() { // orm + ajax requester
		_notImplem('orm');
		return {
			type: 'interface',
			save: function() { _notImplem('orm', 'save'); },
			fetch: function() { _notImplem('orm', 'fetch'); },
			del: function() { _notImplem('orm', 'del'); },
			reset: function() { _notImplem('orm', 'reset'); },
			notify: function() { _notImplem('orm', 'notify'); }
		};
	};

	// PIPES for notifications
	// --------------------
	_mod.pipe = {
		page: {
			notify: function(message, data) {
				_mod.debug.log('page : ' + message, 'all', 'log', data);
				tetra.controller.notify(message, data);
				return this;
			}
		},

		appBuilder: function(target, scope) {
			if(target === 'view') {
				return {
					notify: function(message, data) {
						_mod.debug.log('app ' + scope + ' : ' + target + ' : ' + message, scope, 'log', data);
						tetra.view.notify(message, data, scope);
						return this;
					}
				};
			} else {
				return {
					notify: function(message, data) {
						_mod.debug.log('app ' + scope + ' : ' + target + ' : ' + message, scope, 'log', data);
						tetra.controller.notify(message, data, scope);
						return this;
					},
					exec: function() { // actionName, ctrlName (optional), data, cbk
						var
							args = [],
							i = 0,
							len = arguments.length
						;
						for(; i < len; i++) {
							args.push(arguments[i]);
						}
						args.push(scope);
						return core.controller.exec.apply(this, args);
					}
				};
			}
		}
	};

	// MVC interface
	// Implemented by native modules view, controller and model
	// --------------------
	_core.view = {
		register: function() { _notImplem('view', 'register'); },
		destroy: function() { _notImplem('view', 'destroy'); },
		notify: function() { _notImplem('view', 'notify'); },
		debug: {}
	};

	_core.controller = {
		register: function() { _notImplem('controller', 'register'); },
		destroy: function() { _notImplem('controller', 'destroy'); },
		notify: function() { _notImplem('controller', 'notify'); },
		modelNotify: function() { _notImplem('controller', 'modelNotify'); },
		exec: function() { _notImplem('controller', 'exec'); },
		debug: {}
	};

	_core.model = {
		register: function() { _notImplem('model', 'register'); },
		destroy: function() { _notImplem('model', 'destroy'); },
		debug: {}
	};

	// Logger + debugger
	// --------------------
	_core.debug = (function(){
		var
			_debugApp = {},
			debugLog = function(msg, scope, type, data) {
				scope = scope || 'all';
				if(typeof window.console !== 'undefined' && (_debugApp[scope] || (scope !== 'all' && _debugApp.all))){
					data = data || '';
					type = type || 'log';
					try {
						console[type](msg, data);
					} catch(e){}
				}
			}
		;

		_mod.debug = {
			log: function() {}
		};

		return {
			enable: function(scope) {
				scope = scope || 'all';
				_debugApp[scope] = true;

				// Allow access to underscore lib outside of tetra apps
				window._ = _;

				// Initialize internal and external debug objects
				_mod.debug = {
					log : debugLog
				};
				_core.debug = {
					enable : this.enable,

					man : function() {
						debugLog('Tetra.js ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
						debugLog('> debug enabled');
						debugLog('> man specs > tetra.debug.man()');
						debugLog('	# View specs			> tetra.debug.view.man()');
						debugLog('	# Controller specs		> tetra.debug.controller.man()');
						debugLog('	# Model specs			> tetra.debug.model.man()');
						debugLog('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');

						return true;
					},

					view: _core.view.debug,

					ctrl : _core.controller.debug,

					model: _core.model.debug
				};
			}
		};
	})();

	// Extend core functionality with new modules
	// --------------------
	_core.extend = (function() {

		var
			_add = function(name, module) {
				if(name == 'conf') {
					for(var attr in module) {
						_conf[attr] = module[attr];
					}
				} else {
					if(_started) {
						_addModule(name, module);
					} else {
						_tmp[name] = module;
						_mod.debug.log('Module "'+ name +'" defined');
					}
				}
			},

			_addModule = function(name, module) {
				if(typeof _core[name] !== 'undefined') {
					_core[name] = module(_conf, _mod, _);
					_mod.debug.log('Tetra module "'+ name +'" loaded');
				} else {
					if(name == 'lib') {
						_mod[name] = _ = module(_conf, _mod, _);
					} else {
						_mod[name] = module(_conf, _mod, _);
					}
					_mod.debug.log('Extension module "'+ name +'" loaded');
				}
			},

			_extend = function() {
				if(typeof arguments[0] !== 'undefined') {
					if(typeof arguments[0] === 'string') {
						_add(arguments[0], arguments[1]);
					} else {
						for(var name in arguments[0]) {
							_add(name, arguments[0][name]);
						}
					}
				}

				return _core;
			},

			_start = function() {
				_started = true;

				// start all module in the temporary stack
				for(var name in _tmp) {
					_addModule(name, _tmp[name]);
				}

				// empty the temporary module stack
				_tmp = [];
			}
		;

		// Start the core when all dependencies and conf are set
		// --------------------
		_core.start = _start;

		return _extend;
	})();

	// Initialization of core
	// --------------------

	// Public functions
	return _core;

})();

if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
	exports = module.exports = {};
	exports.tns = tns;
	exports.tetra = tetra;
}
<|endoftext|>


<commit_before>/* global angular */

angular
	.module(`${window.APP_NAME}.common`)
	.directive('video', videoDirective);

const TEMPLATE = `

			<checkbox label="Hide Duration" value="hideDuration"></checkbox>

				{{object.stage.name}}

					{{object.tournament.name}}

				{{object.format.name}}

						Show teams

							{{object.teams.teams[0].name}}

						vs.

							{{object.teams.teams[1].name}}

					Casted by:

						{{caster.name}},

		<rating video="object"></rating>

`;

function videoDirective ($sce, Constants) {

	return {
		restrict: 'E',
		replace: true,
		scope: {
			object: '='
		},
		template: TEMPLATE,
		link: link
	};

	function link (scope) {

		scope.isPlaying = false;
		scope.showTeams = false;
		scope.hideDuration = true;

		scope.getIframeSrc = getIframeSrc;
		scope.toggleTeams = toggleTeams;
		scope.play = play;

		scope.$watch('object', function (newValue) {
			if (!newValue || !newValue.stage) return;
			if (newValue.stage) scope.object.stage.name = Constants.Stages[newValue.stage.code];
			if (newValue.format) scope.object.format.name = Constants.Formats[newValue.format.code];
		});

		function getIframeSrc () {
			var controls = scope.hideDuration ? 'controls=0&' : '',
				url = `https://www.youtube-nocookie.com/embed/${scope.object.youtubeId}?autoplay=1&rel=0&${controls}showinfo=0"`;

			return $sce.trustAsResourceUrl(url);
		}

		function play () {
			scope.isPlaying = true;
		}

		function toggleTeams () {
			scope.showTeams = !scope.showTeams;
			return false;
		}

	}

}
<commit_msg>changed domain for youtube<commit_after>/* global angular */

angular
	.module(`${window.APP_NAME}.common`)
	.directive('video', videoDirective);

const TEMPLATE = `

			<checkbox label="Hide Duration" value="hideDuration"></checkbox>

				{{object.stage.name}}

					{{object.tournament.name}}

				{{object.format.name}}

						Show teams

							{{object.teams.teams[0].name}}

						vs.

							{{object.teams.teams[1].name}}

					Casted by:

						{{caster.name}},

		<rating video="object"></rating>

`;

function videoDirective ($sce, Constants) {

	return {
		restrict: 'E',
		replace: true,
		scope: {
			object: '='
		},
		template: TEMPLATE,
		link: link
	};

	function link (scope) {

		scope.isPlaying = false;
		scope.showTeams = false;
		scope.hideDuration = true;

		scope.getIframeSrc = getIframeSrc;
		scope.toggleTeams = toggleTeams;
		scope.play = play;

		scope.$watch('object', function (newValue) {
			if (!newValue || !newValue.stage) return;
			if (newValue.stage) scope.object.stage.name = Constants.Stages[newValue.stage.code];
			if (newValue.format) scope.object.format.name = Constants.Formats[newValue.format.code];
		});

		function getIframeSrc () {
			var controls = scope.hideDuration ? 'controls=0&' : '',
				url = `https://www.youtube.com/embed/${scope.object.youtubeId}?autoplay=1&rel=0&${controls}showinfo=0"`;

			return $sce.trustAsResourceUrl(url);
		}

		function play () {
			scope.isPlaying = true;
		}

		function toggleTeams () {
			scope.showTeams = !scope.showTeams;
			return false;
		}

	}

}
<|endoftext|>


<commit_before>const Koa = require('koa');
const helper = require('think-helper');
const path = require('path');
const pkg = require('../package.json');
const bluebird = require('bluebird');
const Loader = require('think-loader');
const getConfigFn = require('think-config').getConfigFn;
const assert = require('assert');
const fs = require('fs');
const Logger = require('think-logger');

// ThinkJS root path
const thinkPath = path.join(__dirname, '..');

/**
 * use bluebird instead of default Promise
 */
global.Promise = bluebird;

/**
 * global think object
 * @type {Object}
 */
global.think = Object.create(helper);

/**
 * Koa application instance
 * @type {Koa}
 */
think.app = new Koa();

/**
 * thinkjs version
 * @param  {) []
 * @return {String}     []
 */
think.version = pkg.version;

/**
 * base controller class
 */
think.Controller = class Controller {
  constructor(ctx){
    this.ctx = ctx;
  }
}

/**
 * get controller instance
 */
think.controller = (controller, ctx, m) => {

}

/**
 * base logic class
 */
think.Logic = class Logic extends think.Controller {};

/**
 * get logic instance
 */
think.logic = (logic, ctx, m) => {

}

// before start server
let promises = [];
think.beforeStartServer = fn => {
  if(fn) {
    assert(helper.isFunction(fn), 'fn in think.beforeStartServer must be a function');
    return promises.push(fn());
  }
  const promise = Promise.all(promises);
  const timeout = think.config('startServerTimeout');
  assert(help.isNumber(timeout), 'startServerTimeout must be a number');
  const timeoutPromise = helper.timeout(timeout).then(() => {
    const err = new Error(`waiting for start server timeout, time: ${timeout}ms`);
    return Promise.reject(err);
  });
  return Promise.race([promise, timeoutPromise]);
}

/**
 * load data
 */
const loadData = () => {
  let config = think.loader.loadConfig(think.app.env);
  let modules = think.loader.modules;

  think.config = getConfigFn(config, modules.length > 0);
  //add data to koa application
  think.app.modules = modules;
  think.app.controllers = think.loader.loadController();
  think.app.logics = think.loader.loadLogic();
  think.app.models = think.loader.loadModel();
  think.app.services = think.loader.loadService();
  think.app.routers = think.load.loadRouter();
}

/**
 * load middleware
 */
const loadMiddleware = () => {
  const middlewares = think.loader.loadMiddleware();
  middlewares.forEach(middleware => {
    think.app.use(middleware);
  });
}

/**
 * create think.logger
 */
const createLogger = () => {
  let config = helper.parseAdapterConfig(think.config('logger'));
  think.logger = new Logger(config);
}

/**
 * load extend
 */
const loadExtend = () => {
  let exts = think.loader.loadExtend();
  const list = [
    ['context', think.app.context],
    ['request', think.app.request],
    ['response', think.app.response],
    ['think', think, true],
    ['controller', think.Controller.prototype],
    ['logic', think.Logic.prototype]
  ];
  list.forEach(item => {
    if(!exts[item[0]]) return;
    if(item[2]){
      for(let t in exts[item[0]]){
        assert(!item[1][t], `${item[0]}.${t} can not override`);
        item[1][t] = exts[item[0]][t];
      }
    }else{
      item[1] = Object.assign(item[1], exts[item[0]]);
    }
  })
}

/**
 * init
 */
module.exports = options => {
  think.ROOT_PATH = options.ROOT_PATH;
  think.APP_PATH = options.APP_PATH;
  if(options.env){
    think.app.env = options.env;
  }
  if(options.proxy){
    think.app.proxy = options.proxy;
  }
  think.loader = new Loader(think.APP_PATH, thinkPath);
  loadData();
  loadMiddleware();
  loadExtend();
  createLogger();
}<commit_msg>Fix some syntax errors<commit_after>const Koa = require('koa');
const helper = require('think-helper');
const path = require('path');
const pkg = require('../package.json');
const bluebird = require('bluebird');
const Loader = require('think-loader');
const getConfigFn = require('think-config').getConfigFn;
const assert = require('assert');
const fs = require('fs');
const Logger = require('think-logger3');

// ThinkJS root path
const thinkPath = path.join(__dirname, '..');

/**
 * use bluebird instead of default Promise
 */
global.Promise = bluebird;

/**
 * global think object
 * @type {Object}
 */
global.think = Object.create(helper);

/**
 * Koa application instance
 * @type {Koa}
 */
think.app = new Koa();

/**
 * thinkjs version
 * @param  {) []
 * @return {String}     []
 */
think.version = pkg.version;

/**
 * base controller class
 */
think.Controller = class Controller {
  constructor(ctx){
    this.ctx = ctx;
  }
}

/**
 * get controller instance
 */
think.controller = (controller, ctx, m) => {

}

/**
 * base logic class
 */
think.Logic = class Logic extends think.Controller {};

/**
 * get logic instance
 */
think.logic = (logic, ctx, m) => {

}

// before start server
let promises = [];
think.beforeStartServer = fn => {
  if(fn) {
    assert(helper.isFunction(fn), 'fn in think.beforeStartServer must be a function');
    return promises.push(fn());
  }
  const promise = Promise.all(promises);
  const timeout = think.config('startServerTimeout');
  assert(helper.isNumber(timeout), 'startServerTimeout must be a number');
  const timeoutPromise = helper.timeout(timeout).then(() => {
    const err = new Error(`waiting for start server timeout, time: ${timeout}ms`);
    return Promise.reject(err);
  });
  return Promise.race([promise, timeoutPromise]);
}

/**
 * load data
 */
const loadData = () => {
  let config = think.loader.loadConfig(think.app.env);
  let modules = think.loader.modules;

  think.config = getConfigFn(config, modules.length > 0);
  //add data to koa application
  think.app.modules = modules;
  think.app.controllers = think.loader.loadController();
  think.app.logics = think.loader.loadLogic();
  think.app.models = think.loader.loadModel();
  think.app.services = think.loader.loadService();
  think.app.routers = think.loader.loadRouter();
}

/**
 * load middleware
 */
const loadMiddleware = () => {
  const middlewares = think.loader.loadMiddleware();
  middlewares.forEach(middleware => {
    think.app.use(middleware);
  });
}

/**
 * create think.logger
 */
const createLogger = () => {
  let config = helper.parseAdapterConfig(think.config('logger'));
  think.logger = new Logger(config);
}

/**
 * load extend
 */
const loadExtend = () => {
  let exts = think.loader.loadExtend();
  const list = [
    ['context', think.app.context],
    ['request', think.app.request],
    ['response', think.app.response],
    ['think', think, true],
    ['controller', think.Controller.prototype],
    ['logic', think.Logic.prototype]
  ];
  list.forEach(item => {
    if(!exts[item[0]]) return;
    if(item[2]){
      for(let t in exts[item[0]]){
        assert(!item[1][t], `${item[0]}.${t} can not override`);
        item[1][t] = exts[item[0]][t];
      }
    }else{
      item[1] = Object.assign(item[1], exts[item[0]]);
    }
  })
}

/**
 * init
 */
module.exports = options => {
  think.ROOT_PATH = options.ROOT_PATH;
  think.APP_PATH = options.APP_PATH;
  if(options.env){
    think.app.env = options.env;
  }
  if(options.proxy){
    think.app.proxy = options.proxy;
  }
  think.loader = new Loader(think.APP_PATH, thinkPath, think.app);
  loadData();
  loadMiddleware();
  loadExtend();
  createLogger();
}<|endoftext|>


<commit_before>import { applyMiddleware,
         createStore } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import createLogger from 'redux-logger';

import { reducer } from '~reslab/redux/reducer';

const logger = createLogger();
const store = createStore(reducer, applyMiddleware(thunk, promise, logger));

// Taken from https://github.com/reactjs/redux/issues/303#issuecomment-125184409
//
// This function implements an observer pattern on the store.
//
// To observe, call this function with an optional selector function and a
// change handler. Whenever the store dispatches an action, the selector is used
// to extract some part of the new state, compare it to the last state, and, if
// they are known to not be the same object, the change handler is invoked on
// both the new state and the old one.
const observe = (store, onChange, selector) => {
  let lastState;

  const handler = () => {
    let nextState = store.getState();
    if (!lastState || selector(nextState) !== selector(lastState)) {
      onChange(nextState, lastState);
      lastState = nextState;
    }
  };

  let unsubscribe = store.subscribe(handler);
  handler();

  return unsubscribe;
};

// A convenience function that points observe() at the application store.
const observeStore = (onChange, selector = v => v) => observe(store, onChange, selector);

export {
  store,
  observe,
  observeStore
};
<commit_msg>Convert immutable state to plain JS object before logging<commit_after>import { applyMiddleware,
         createStore } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import createLogger from 'redux-logger';

import { reducer } from '~reslab/redux/reducer';

const logger = createLogger({
  stateTransformer: s => s.toJS()
});
const store = createStore(reducer, applyMiddleware(thunk, promise, logger));

// Taken from https://github.com/reactjs/redux/issues/303#issuecomment-125184409
//
// This function implements an observer pattern on the store.
//
// To observe, call this function with an optional selector function and a
// change handler. Whenever the store dispatches an action, the selector is used
// to extract some part of the new state, compare it to the last state, and, if
// they are known to not be the same object, the change handler is invoked on
// both the new state and the old one.
const observe = (store, onChange, selector) => {
  let lastState;

  const handler = () => {
    let nextState = store.getState();
    if (!lastState || selector(nextState) !== selector(lastState)) {
      onChange(nextState, lastState);
      lastState = nextState;
    }
  };

  let unsubscribe = store.subscribe(handler);
  handler();

  return unsubscribe;
};

// A convenience function that points observe() at the application store.
const observeStore = (onChange, selector = v => v) => observe(store, onChange, selector);

export {
  store,
  observe,
  observeStore
};
<|endoftext|>


<commit_before>// TODO Refactor Tile to own class ?
var fs = require('fs');
var gm = require('gm');

var Promise = require('bluebird');
Promise.promisifyAll(gm.prototype);

var util = require('./util');

var Tiler = function (filePath) {
  // // Took 80s for Heroku to serve a 26.6MB pic
  // // 26.6MB / (80 * 4) = 83KBps
  // // Goal: <100KB per tile
  this.GOAL_MAX_TILE_SIZE = 100000;
  this.filePath = filePath;

  this.compressionLevels = { }; // keys of zoomLevels and vals of compressionLevels
  this.size; // hash containing props .width and .height, in pixels of original image
  this.maxZoomLevel; // int
};
Tiler.prototype.genTile = Promise.method(function (zoomLevel, i, j, next) {
  // TODO ? Refactor to reduce number of readStreams created ?
  var readStream = fs.createReadStream(this.filePath);
  // DUP SIDELENGTH refactor to new Tiler.zoomLevel ?
  var sideLength = Math.pow(2, zoomLevel);

  var tileWidth = this.size.width / sideLength;
  var tileHeight = this.size.height / sideLength;
  var tileX = j * tileWidth;
  var tileY = i * tileHeight;

  return gm(readStream)
      .crop(tileWidth, tileHeight, tileX, tileY)
      // TODO #quality destroys the image color.  Find some other way to
      // reduce file size ?
      .quality(this.compressionLevels[zoomLevel])
      .writeAsync(util.genFileName(this.filePath, i, j, zoomLevel));
});
Tiler.prototype.genTileSetForZoomLevel = Promise.method(function (zoomLevel, next) {
  var _this = this;
  console.log('genTileSetForZoomLevel', zoomLevel);
  // DUP SIDELENGTH refactor to new Tiler.zoomLevel ?
  var sideLength = Math.pow(2, zoomLevel);

  var sub = Promise.method(function (zoomLevel, rowIdx, next) {
    if (rowIdx >= sideLength) return zoomLevel + 1;

    console.log('processing row:', rowIdx);
    return this.genTilesForRow.call(_this, zoomLevel, rowIdx)
        .then(function (genTilesForRowRes) {
          console.log('genTilesForRowRes', genTilesForRowRes);
          // DUP ITER PROMISE
          return sub.call(_this, zoomLevel, rowIdx + 1);
        });
  });

  return sub.call(_this, zoomLevel, 0).bind(_this); // TODO ? refactor #bind ?
});
Tiler.prototype.genTilesForRow = Promise.method(function (zoomLevel, rowIdx, next) {
  // DUP SIDELENGTH refactor to new Tiler.zoomLevel ?
  var sideLength = Math.pow(2, zoomLevel);
  var _this = this;

  var sub = Promise.method(function (zoomLevel, rowIdx, colIdx, next) {
    if (colIdx >= sideLength) return 'row done';

    console.log('processing tile at i:', rowIdx, 'j:', colIdx);
    return _this.genTile(zoomLevel, rowIdx, colIdx)
        .then(function (err) {
          if (err) console.error(err);
          // DUP ITER PROMISE
          return sub.call(_this, zoomLevel, rowIdx, colIdx + 1);
        });
  });

  // DUP ITER PROMISE
  return sub.call(_this, zoomLevel, rowIdx, 0);
});
Tiler.prototype.genTileSets = Promise.method(function (next) {

  var sub = Promise.method(function (zoomLevel, next) {
    if (zoomLevel > 2) {
    // if (zoomLevel > this.maxZoomLevel) { // TODO
      return '#genTileSets done';
    } else {
      console.log('processing zoomLevel:', zoomLevel);
      return this.genTileSetForZoomLevel(zoomLevel)
          .then(function (nextZoomLevel) {
            // DUP ITER PROMISE
            return sub.call(this, nextZoomLevel);
          });
    }
  });

  return sub.call(this, 0);
});
Tiler.prototype.setCompressionLevels = function (next) {
// TODO Make this not so naive
  for (var zoomLevel = this.maxZoomLevel; zoomLevel >= 0; zoomLevel--) {
    var denominator = this.maxZoomLevel - (zoomLevel - 1);
    this.compressionLevels[zoomLevel] = 1 / denominator;
  }
  console.log("COMPRESSION", this.compressionLevels);
  next();
};
Tiler.prototype.setMaxZoomLevel = function (next) {
  // TODO refactor to recurse on cropped image, not original image, to increase speed
  function tryZoomLevel (zoomLevel) {
    var readStream = fs.createReadStream(this.filePath);
    var writeStream = fs.createWriteStream(this.filePath + '_copy.jpg');

    var width = this.size.width / Math.pow(2, zoomLevel);
    var height = this.size.height / Math.pow(2, zoomLevel);

    var _this = this;
    gm(readStream)
        .crop(width, height, 0, 0)
        .stream(function (err, stdout, stderr) {
          writeStream.on('close', function () {
            if (writeStream.bytesWritten > _this.GOAL_MAX_TILE_SIZE) {
              // DUP ITER PROMISE
              return tryZoomLevel.call(_this, zoomLevel + 1);
            } else {
              _this.maxZoomLevel = zoomLevel;
              return next();
            }
          });
          stdout.pipe(writeStream);
        })
  }
  tryZoomLevel.call(this, 0);
};
Tiler.prototype.setOriginalImageSize = function (next) {
  var readStream = fs.createReadStream(this.filePath);
  var _this = this;

  gm(readStream)
      .size({ bufferStream: true }, function (err, size) {
        if (err) { return console.error(err); }

        _this.size = size;
        next();
      });
};

module.exports = Tiler;
<commit_msg>(refactor) Refactor to Tiler#SIDE_LENGTH<commit_after>// TODO Refactor Tile to own class ?
var fs = require('fs');
var gm = require('gm');

var Promise = require('bluebird');
Promise.promisifyAll(gm.prototype);

var util = require('./util');

var Tiler = function (filePath) {
  // // Took 80s for Heroku to serve a 26.6MB pic
  // // 26.6MB / (80 * 4) = 83KBps
  // // Goal: <100KB per tile
  this.GOAL_MAX_TILE_SIZE = 100000;
  this.SIDE_LENGTH = function (zoomLevel) { return Math.pow(2, zoomLevel); };
  this.filePath = filePath;

  this.compressionLevels = { }; // keys of zoomLevels and vals of compressionLevels
  this.size; // hash containing props .width and .height, in pixels of original image
  this.maxZoomLevel; // int
};
Tiler.prototype.genTile = Promise.method(function (zoomLevel, i, j, next) {
  var readStream = fs.createReadStream(this.filePath);

  var tileWidth = this.size.width / this.SIDE_LENGTH(zoomLevel);
  var tileHeight = this.size.height / this.SIDE_LENGTH(zoomLevel);
  var tileX = j * tileWidth;
  var tileY = i * tileHeight;

  return gm(readStream)
      .crop(tileWidth, tileHeight, tileX, tileY)
      // TODO #quality destroys the image color.  Find some other way to
      // reduce file size ?
      .quality(this.compressionLevels[zoomLevel])
      .writeAsync(util.genFileName(this.filePath, i, j, zoomLevel));
});
Tiler.prototype.genTileSetForZoomLevel = Promise.method(function (zoomLevel, next) {
  var _this = this;
  console.log('genTileSetForZoomLevel', zoomLevel);

  var sub = Promise.method(function (zoomLevel, rowIdx, next) {
    // DUP IS_IN_BOUNDS abstract out to own func IS_IN_BOUNDS ?
    if (rowIdx >= this.SIDE_LENGTH(zoomLevel)) return zoomLevel + 1;

    console.log('processing row:', rowIdx);
    return this.genTilesForRow.call(_this, zoomLevel, rowIdx)
        .then(function (genTilesForRowRes) {
          console.log('genTilesForRowRes', genTilesForRowRes);
          // DUP ITER PROMISE
          return sub.call(_this, zoomLevel, rowIdx + 1);
        });
  });

  return sub.call(_this, zoomLevel, 0).bind(_this); // TODO ? refactor #bind ?
});
Tiler.prototype.genTilesForRow = Promise.method(function (zoomLevel, rowIdx, next) {
  var _this = this;

  var sub = Promise.method(function (zoomLevel, rowIdx, colIdx, next) {
    // DUP IS_IN_BOUNDS abstract out to own func IS_IN_BOUNDS ?
    if (colIdx >= this.SIDE_LENGTH(zoomLevel)) return 'row done';

    console.log('processing tile at i:', rowIdx, 'j:', colIdx);
    return _this.genTile(zoomLevel, rowIdx, colIdx)
        .then(function (err) {
          if (err) console.error(err);
          // DUP ITER PROMISE
          return sub.call(_this, zoomLevel, rowIdx, colIdx + 1);
        });
  });

  // DUP ITER PROMISE
  return sub.call(_this, zoomLevel, rowIdx, 0);
});
Tiler.prototype.genTileSets = Promise.method(function (next) {

  var sub = Promise.method(function (zoomLevel, next) {
    if (zoomLevel > 2) {
    // if (zoomLevel > this.maxZoomLevel) { // TODO
      return '#genTileSets done';
    } else {
      console.log('processing zoomLevel:', zoomLevel);
      return this.genTileSetForZoomLevel(zoomLevel)
          .then(function (nextZoomLevel) {
            // DUP ITER PROMISE
            return sub.call(this, nextZoomLevel);
          });
    }
  });

  return sub.call(this, 0);
});
Tiler.prototype.setCompressionLevels = function (next) {
// TODO Make this not so naive
  for (var zoomLevel = this.maxZoomLevel; zoomLevel >= 0; zoomLevel--) {
    var denominator = this.maxZoomLevel - (zoomLevel - 1);
    this.compressionLevels[zoomLevel] = 1 / denominator;
  }
  console.log("COMPRESSION", this.compressionLevels);
  next();
};
Tiler.prototype.setMaxZoomLevel = function (next) {
  // TODO refactor to recurse on cropped image, not original image, to increase speed
  function tryZoomLevel (zoomLevel) {
    var readStream = fs.createReadStream(this.filePath);
    var writeStream = fs.createWriteStream(this.filePath + '_copy.jpg');

    var width = this.size.width / Math.pow(2, zoomLevel);
    var height = this.size.height / Math.pow(2, zoomLevel);

    var _this = this;
    gm(readStream)
        .crop(width, height, 0, 0)
        .stream(function (err, stdout, stderr) {
          writeStream.on('close', function () {
            if (writeStream.bytesWritten > _this.GOAL_MAX_TILE_SIZE) {
              // DUP ITER PROMISE
              return tryZoomLevel.call(_this, zoomLevel + 1);
            } else {
              _this.maxZoomLevel = zoomLevel;
              return next();
            }
          });
          stdout.pipe(writeStream);
        })
  }
  tryZoomLevel.call(this, 0);
};
Tiler.prototype.setOriginalImageSize = function (next) {
  var readStream = fs.createReadStream(this.filePath);
  var _this = this;

  gm(readStream)
      .size({ bufferStream: true }, function (err, size) {
        if (err) { return console.error(err); }

        _this.size = size;
        next();
      });
};

module.exports = Tiler;
<|endoftext|>


<commit_before>(function () {

  // Convienience aliases
  var location  = window.location,
      history   = window.history;

  // -- Utility ---------------------------------------------------------------

  /**
  binds a function to a context

  @method bind
  @param {Function} func
  @param {Object} context
  @return {Function}
  **/
  var bind = function (func, context) {
    return function () {
      func.apply(context, Array.prototype.slice.call(arguments));
    };
  };

  /**
  @method each
  @param {Array} arr
  @param {Function} iterator

  @private
  **/
  var each = function (arr, iterator, context) {
    context = context || this;

    for (var i = 0, len = arr.length; i < len; i++) {
      iterator.call(context, arr[i], i);
    }
  };

  /**
  @method merge
  @return {Object}
  **/
  var merge = function () {
    var result = {},
        arr = Array.prototype.slice.call(arguments, 0);

    each(arr, function (obj) {
      for (var key in obj) {
        result[key] = obj[key];
      }
    });

    return result;
  };

  /**
  @method addEvent
  @param {Any} host
  @param {String} eventName
  @param {Function} handler
  @param {Any} [context]
  **/
  var addEvent = function (host, eventName, handler, context) {
    host.addEventListener(eventName, bind(handler, context), false);
  };

  /**
  @method isPlainObject
  @param {any} val
  @return {Boolean}
  **/
  var isPlainObject = function (val) {
    return (!!val) && (val.constructor === Object);
  };

  /**
  @method isString
  @param {Any} val
  @return {Boolean}
  **/
  var isString = function (val) {
    return typeof val === 'string';
  };

  // -- Internal API ----------------------------------------------------------

  /**
  @class Request
  @constructor
  **/
  var Request = function (options) {
    this.namedParams  = {};
    this.queryParams  = {};
    this.params       = {};

    this.uri          = options.uri;
    this.queryString  = options.queryString;
    this.matchedRoute = options.matchedRoute;

    this._extractNamedParamsFromURI();
    this._extractQueryParamsFromQueryString();
    this._mergeParams();
  };

  Request.prototype = {
    /**
    @method _extractNamedParamsFromURI
    @private
    **/
    _extractNamedParamsFromURI: function () {
      var uriParts = this.uri.split('/'),
          routeParts = this.matchedRoute.split('/'),
          params = {};

      each(routeParts, function (part, i) {
        var key;

        if (part.indexOf(':') === 0) {
          key = part.replace(':', '');

          params[key] = uriParts[i];
        }
      });

      this.namedParams = params;
    },

    /**
    Splits the query string by '&'. Splits each part by '='.
    Passes the key and value for each part to _applyQueryParam

    @method _extractQueryParamsFromQueryString
    @private
    **/
    _extractQueryParamsFromQueryString: function () {
      var parts;

      if (!this.queryString) return;

      parts = this.queryString.replace('?','').split('&');

      each(parts, function (part) {
        var key = part.split('=')[0],
            val = part.split('=')[1];

        if ( part.indexOf( '=' ) === -1 ) return;
        this._applyQueryParam( key, val );

      }, this);

    },

    /**
    Update the queryParams property with a new key and value.
    Values for keys with the [] notation are put into arrays
    or pushed into an existing array for that key.

    @method _applyQueryParam
    @param {String} key
    @param {String} val
    **/
    _applyQueryParam: function (key, val) {
      if ( key.indexOf( '[]' ) !== -1 ) {
        key = key.replace( '[]', '' );

        if ( this.queryParams[key] instanceof Array ) {
          this.queryParams[key].push(val);
        } else {
          this.queryParams[key] = [ val ];
        }
      }
      else {
        this.queryParams[key] = val;
      }
    },

    /**
    @method _mergeParams
    @private
    **/
    _mergeParams: function () {
      this.params = merge(this.namedParams, this.queryParams);
    }
  };

  /**
  Contains the properties for a route
  After attempting to match a uri to the Routes map

  @class Route
  @constructor
  @private
  **/
  var Route = function (routes, uri) {
    this.uri          = uri;
    this.matchedRoute = '';
    this.actions      = [];
    this.options      = {};

    this.match(routes);

    this.uri = uri;
  };

  Route.prototype = {

    /**
    Matches the uri from the routes map.

    @method match
    @param {String} routeLevel
    @return {Object}
    **/
    match: function (routeLevel) {
      var value,
          action = {
            target: routeLevel.target,
            method:    null
          };

      for (var key in routeLevel) {
        value = routeLevel[key];

        if (this.isFragment(key) && this.isFragmentInURI(key)) {
          this.updateMatchedRoute(key);
          this.updateURI(key);

          if (this.isActionDescriptor(value)) {

            // Check that if this fragment is a namedParam,
            // we never override a regular fragment.
            if (!this.isNamedParam(key) || !action.method) {
              if (isString(value)) {
                action.method = value;
              }
              else {
                action.method = value.method;
                this.mergeOptions(value.options);
              }
              this.actions.push(action);
            }
          }

          if (isPlainObject(value)) {
            // recurse
            this.match(value);
          }
        }
      }
    },

    /**
    @method mergeOptions
    @param {Object} options
    **/
    mergeOptions: function (options) {
      this.options = merge(this.options, options);
    },

    /**
    appends the matched fragment to the matched route

    @method updateMatchedRoute
    @param {String} fragment
    **/
    updateMatchedRoute: function (fragment) {
      if (fragment !== '/' && fragment !== '/*') {
        this.matchedRoute += fragment;
      }
    },

    /**
    removes matched fragments from the beginning of the uri

    @method updateURI
    @param {String} fragment
    **/
    updateURI: function (fragment) {
      var uri = this.uri,
          match;

      if (fragment !== '/' && fragment !== '/*') {
        match = uri.match(/(\/\w+)\/?/);
        if (match) {
          this.uri = uri.replace(match[1], '');
        }
      }
    },

    /**
    @method isFragmentInURI
    @param {Any} fragment
    @return {Boolean}
    **/
    isFragmentInURI: function (fragment) {
      var uri    = this.uri,
          match;

      if ( fragment === '/' ) {
        return (uri === '/' || uri === '');
      }
      else if ( fragment === '/*' ) {
        return true;
      }
      else if ( this.isNamedParam(fragment) ) {
        return (uri !== '/' && uri !== '');
      }
      else {
        match = uri.match(/(\/\w+)\/?/);
        return match && fragment === match[1];
      }
    },

    /**
    @method isFragment
    @param {Any} key
    @return {Boolean}
    **/
    isFragment: function (key) {
      return key.indexOf('/') === 0;
    },

    /**
    @method isActionDescriptor
    @param {Any} val
    @return {Boolean}
    **/
    isActionDescriptor: function (val) {
      return isString(val) || isPlainObject(val) && val.method && val.options;
    },

    /**
    @method isNamedParam
    @param {String} fragment
    @return {Boolean}
    **/
    isNamedParam: function (fragment) {
      return fragment.indexOf('/:') === 0;
    }
  };

  /**
  @class Navigator
  @constructor
  @private
  **/
  var Navigator = function () {
    this._routes = null;
  };

  Navigator.prototype = {

    /**
    @method setup
    @param {Object} options
    **/
    setup: function (options) {
      options = options || {};

      for (var k in options) {
        this[k] = options[k];
      }

      this._attachEvents();
    },

    /**
    @method setRoutes
    @param {Object} routes a configuration of routes and targets
    **/
    setRoutes: function (routes) {
      this._routes = routes;
    },

    /**
    @method getRouteForURI
    @param {String} uri
    @return {Request}
    **/
    getRouteForURI: function (uri) {
      return new Route(this._routes, uri);
    },

    /**
    @method getRequest
    @param {String} uri
    @param {String|Null} queryString
    @param {String} matchedRoute
    @return {Request}
    **/
    getRequest: function (uri, queryString, matchedRoute) {
      return new Request({
        uri: uri,
        queryString: queryString,
        matchedRoute: matchedRoute
      });
    },

    /**
    @method getURI
    @return {String}
    **/
    getURI: function () {
      if (this.pushStateEnabled) {
        return location.pathname.replace(this.root, '');
      }
      else {
        return location.hash.replace('#', '');
      }
    },

    /**
    @method getQueryString
    @return {String|Null}
    **/
    getQueryString: function () {
      return window.location.search || null;
    },

    /**
    @method dispatch
    **/
    dispatch: function () {
      var uri         = this.getURI(),
          route       = this.getRouteForURI(uri),
          queryString = this.getQueryString(),
          options     = route.options,
          request     = this.getRequest(
            uri,
            queryString,
            route.matchedRoute
          );

      each(route.actions, function (action) {
        var target = action.target,
            method = action.method;

        if (!(method in target)) {
          throw new Error("Can't call " + method + ' on target for uri ' + request.uri);
        }

        target[method].call(
          target,
          request,
          options
        );
      });
    },

    /**
    @method onURIChange
    **/
    onURIChange: function () {
      this.dispatch();
    },

    /**
    @method onPopState
    **/
    onPopState: function () {
      // some browsers fire 'popstate' on the initial page load
      // with a null state object. In those cases we don't want
      // to trigger the uri change
      if (!history.state) this.onURIChange();
    },

    /**
    @method onClick
    @param {Event} ev
    **/
    onClick: function (ev) {
      var target = ev.target,
          matchesSelector = this._matchesSelector(target),
          uri;

      if (!matchesSelector) return;

      ev.preventDefault();

      uri = target.pathname.replace(this.root, '');

      this.navigate(uri);
    },

    /**
    @method navigate
    @param {String} uri
    **/
    navigate: function (uri) {
      if (this.pushStateEnabled) {
        history.pushState(null, '', this.root + uri);
        this.onURIChange();
      }
      else {
        location.hash = uri;
      }
    },

    /**
    @method refresh
    **/
    refresh: function () {
      this.dispatch();
    },

    /**
    @method _attachEvents
    @protected
    **/
    _attachEvents: function () {
      var pushStateEnabled = this.pushStateEnabled,
          evt = (pushStateEnabled ? 'popstate' : 'hashchange');

      if (pushStateEnabled) {
        addEvent(window, 'popstate', this.onPopState, this);
      }
      else {
        addEvent(window, 'hashchange', this.onURIChange, this);
      }

      addEvent(document, 'click', this.onClick, this);
    },

    /**
    @method _matchesSelector
    @param {DOMNode} node
    @protected
    **/
    _matchesSelector: function (node) {
      var nodeList = document.querySelectorAll(this.linkSelector),
          contains = false,
          i;

      for ( i = 0; i < nodeList.length; i++ ) {
        if (!contains) contains = ( node === nodeList[i] );
        else break;
      }

      return contains;
    }

  };

  // -- Public API ------------------------------------------------------------
  // Only expose a tiny API to keep internal routing safe

  /**
  @singleton Aviator
  **/
  window.Aviator = {

    /**
    @property pushStateEnabled
    @type {Boolean}
    @default true if the browser supports pushState
    **/
    pushStateEnabled: ('pushState' in history),

    /**
    @property linkSelector
    @type {String}
    @default 'a.navigate'
    **/
    linkSelector: 'a.navigate',

    /**
    the root of the uri from which routing will append to

    @property root
    @type {String}
    @default ''
    **/
    root: '',

    /**
    @property _navigator
    @type {Navigator}

    @private
    **/
    _navigator: new Navigator(),

    /**
    @method setRoutes
    @param {Object} routes
    **/
    setRoutes: function (routes) {
      this._navigator.setRoutes(routes);
    },

    /**
    dispatches routes to targets and sets up event handlers

    @method dispatch
    **/
    dispatch: function () {
      var navigator = this._navigator;

      navigator.setup({
        pushStateEnabled: this.pushStateEnabled,
        linkSelector:     this.linkSelector,
        root:             this.root
      });

      navigator.dispatch();
    },

    /**
    @method navigate
    @param {String} uri to navigate to
    **/
    navigate: function (uri) {
      this._navigator.navigate(uri);
    },

    /**
    @method refresh
    **/
    refresh: function () {
      this._navigator.refresh();
    }

  };

}());
<commit_msg>Use hasOwnProperty when looping over props in objs<commit_after>(function () {

  // Convienience aliases
  var location  = window.location,
      history   = window.history;

  // -- Utility ---------------------------------------------------------------

  /**
  binds a function to a context

  @method bind
  @param {Function} func
  @param {Object} context
  @return {Function}
  **/
  var bind = function (func, context) {
    return function () {
      func.apply(context, Array.prototype.slice.call(arguments));
    };
  };

  /**
  @method each
  @param {Array} arr
  @param {Function} iterator

  @private
  **/
  var each = function (arr, iterator, context) {
    context = context || this;

    for (var i = 0, len = arr.length; i < len; i++) {
      iterator.call(context, arr[i], i);
    }
  };

  /**
  @method merge
  @return {Object}
  **/
  var merge = function () {
    var result = {},
        arr = Array.prototype.slice.call(arguments, 0);

    each(arr, function (obj) {
      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          result[key] = obj[key];
        }
      }
    });

    return result;
  };

  /**
  @method addEvent
  @param {Any} host
  @param {String} eventName
  @param {Function} handler
  @param {Any} [context]
  **/
  var addEvent = function (host, eventName, handler, context) {
    host.addEventListener(eventName, bind(handler, context), false);
  };

  /**
  @method isPlainObject
  @param {any} val
  @return {Boolean}
  **/
  var isPlainObject = function (val) {
    return (!!val) && (val.constructor === Object);
  };

  /**
  @method isString
  @param {Any} val
  @return {Boolean}
  **/
  var isString = function (val) {
    return typeof val === 'string';
  };

  // -- Internal API ----------------------------------------------------------

  /**
  @class Request
  @constructor
  **/
  var Request = function (options) {
    this.namedParams  = {};
    this.queryParams  = {};
    this.params       = {};

    this.uri          = options.uri;
    this.queryString  = options.queryString;
    this.matchedRoute = options.matchedRoute;

    this._extractNamedParamsFromURI();
    this._extractQueryParamsFromQueryString();
    this._mergeParams();
  };

  Request.prototype = {
    /**
    @method _extractNamedParamsFromURI
    @private
    **/
    _extractNamedParamsFromURI: function () {
      var uriParts = this.uri.split('/'),
          routeParts = this.matchedRoute.split('/'),
          params = {};

      each(routeParts, function (part, i) {
        var key;

        if (part.indexOf(':') === 0) {
          key = part.replace(':', '');

          params[key] = uriParts[i];
        }
      });

      this.namedParams = params;
    },

    /**
    Splits the query string by '&'. Splits each part by '='.
    Passes the key and value for each part to _applyQueryParam

    @method _extractQueryParamsFromQueryString
    @private
    **/
    _extractQueryParamsFromQueryString: function () {
      var parts;

      if (!this.queryString) return;

      parts = this.queryString.replace('?','').split('&');

      each(parts, function (part) {
        var key = part.split('=')[0],
            val = part.split('=')[1];

        if ( part.indexOf( '=' ) === -1 ) return;
        this._applyQueryParam( key, val );

      }, this);

    },

    /**
    Update the queryParams property with a new key and value.
    Values for keys with the [] notation are put into arrays
    or pushed into an existing array for that key.

    @method _applyQueryParam
    @param {String} key
    @param {String} val
    **/
    _applyQueryParam: function (key, val) {
      if ( key.indexOf( '[]' ) !== -1 ) {
        key = key.replace( '[]', '' );

        if ( this.queryParams[key] instanceof Array ) {
          this.queryParams[key].push(val);
        } else {
          this.queryParams[key] = [ val ];
        }
      }
      else {
        this.queryParams[key] = val;
      }
    },

    /**
    @method _mergeParams
    @private
    **/
    _mergeParams: function () {
      this.params = merge(this.namedParams, this.queryParams);
    }
  };

  /**
  Contains the properties for a route
  After attempting to match a uri to the Routes map

  @class Route
  @constructor
  @private
  **/
  var Route = function (routes, uri) {
    this.uri          = uri;
    this.matchedRoute = '';
    this.actions      = [];
    this.options      = {};

    this.match(routes);

    this.uri = uri;
  };

  Route.prototype = {

    /**
    Matches the uri from the routes map.

    @method match
    @param {String} routeLevel
    @return {Object}
    **/
    match: function (routeLevel) {
      var value,
          action = {
            target: routeLevel.target,
            method:    null
          };

      for (var key in routeLevel) {
        if (routeLevel.hasOwnProperty(key)) {
          value = routeLevel[key];

          if (this.isFragment(key) && this.isFragmentInURI(key)) {
            this.updateMatchedRoute(key);
            this.updateURI(key);

            if (this.isActionDescriptor(value)) {

              // Check that if this fragment is a namedParam,
              // we never override a regular fragment.
              if (!this.isNamedParam(key) || !action.method) {
                if (isString(value)) {
                  action.method = value;
                }
                else {
                  action.method = value.method;
                  this.mergeOptions(value.options);
                }
                this.actions.push(action);
              }
            }

            if (isPlainObject(value)) {
              // recurse
              this.match(value);
            }
          }
        }
      }
    },

    /**
    @method mergeOptions
    @param {Object} options
    **/
    mergeOptions: function (options) {
      this.options = merge(this.options, options);
    },

    /**
    appends the matched fragment to the matched route

    @method updateMatchedRoute
    @param {String} fragment
    **/
    updateMatchedRoute: function (fragment) {
      if (fragment !== '/' && fragment !== '/*') {
        this.matchedRoute += fragment;
      }
    },

    /**
    removes matched fragments from the beginning of the uri

    @method updateURI
    @param {String} fragment
    **/
    updateURI: function (fragment) {
      var uri = this.uri,
          match;

      if (fragment !== '/' && fragment !== '/*') {
        match = uri.match(/(\/\w+)\/?/);
        if (match) {
          this.uri = uri.replace(match[1], '');
        }
      }
    },

    /**
    @method isFragmentInURI
    @param {Any} fragment
    @return {Boolean}
    **/
    isFragmentInURI: function (fragment) {
      var uri    = this.uri,
          match;

      if ( fragment === '/' ) {
        return (uri === '/' || uri === '');
      }
      else if ( fragment === '/*' ) {
        return true;
      }
      else if ( this.isNamedParam(fragment) ) {
        return (uri !== '/' && uri !== '');
      }
      else {
        match = uri.match(/(\/\w+)\/?/);
        return match && fragment === match[1];
      }
    },

    /**
    @method isFragment
    @param {Any} key
    @return {Boolean}
    **/
    isFragment: function (key) {
      return key.indexOf('/') === 0;
    },

    /**
    @method isActionDescriptor
    @param {Any} val
    @return {Boolean}
    **/
    isActionDescriptor: function (val) {
      return isString(val) || isPlainObject(val) && val.method && val.options;
    },

    /**
    @method isNamedParam
    @param {String} fragment
    @return {Boolean}
    **/
    isNamedParam: function (fragment) {
      return fragment.indexOf('/:') === 0;
    }
  };

  /**
  @class Navigator
  @constructor
  @private
  **/
  var Navigator = function () {
    this._routes = null;
  };

  Navigator.prototype = {

    /**
    @method setup
    @param {Object} options
    **/
    setup: function (options) {
      options = options || {};

      for (var k in options) {
        if (options.hasOwnProperty(k)) {
          this[k] = options[k];
        }
      }

      this._attachEvents();
    },

    /**
    @method setRoutes
    @param {Object} routes a configuration of routes and targets
    **/
    setRoutes: function (routes) {
      this._routes = routes;
    },

    /**
    @method getRouteForURI
    @param {String} uri
    @return {Request}
    **/
    getRouteForURI: function (uri) {
      return new Route(this._routes, uri);
    },

    /**
    @method getRequest
    @param {String} uri
    @param {String|Null} queryString
    @param {String} matchedRoute
    @return {Request}
    **/
    getRequest: function (uri, queryString, matchedRoute) {
      return new Request({
        uri: uri,
        queryString: queryString,
        matchedRoute: matchedRoute
      });
    },

    /**
    @method getURI
    @return {String}
    **/
    getURI: function () {
      if (this.pushStateEnabled) {
        return location.pathname.replace(this.root, '');
      }
      else {
        return location.hash.replace('#', '');
      }
    },

    /**
    @method getQueryString
    @return {String|Null}
    **/
    getQueryString: function () {
      return window.location.search || null;
    },

    /**
    @method dispatch
    **/
    dispatch: function () {
      var uri         = this.getURI(),
          route       = this.getRouteForURI(uri),
          queryString = this.getQueryString(),
          options     = route.options,
          request     = this.getRequest(
            uri,
            queryString,
            route.matchedRoute
          );

      each(route.actions, function (action) {
        var target = action.target,
            method = action.method;

        if (!(method in target)) {
          throw new Error("Can't call " + method + ' on target for uri ' + request.uri);
        }

        target[method].call(
          target,
          request,
          options
        );
      });
    },

    /**
    @method onURIChange
    **/
    onURIChange: function () {
      this.dispatch();
    },

    /**
    @method onPopState
    **/
    onPopState: function () {
      // some browsers fire 'popstate' on the initial page load
      // with a null state object. In those cases we don't want
      // to trigger the uri change
      if (!history.state) this.onURIChange();
    },

    /**
    @method onClick
    @param {Event} ev
    **/
    onClick: function (ev) {
      var target = ev.target,
          matchesSelector = this._matchesSelector(target),
          uri;

      if (!matchesSelector) return;

      ev.preventDefault();

      uri = target.pathname.replace(this.root, '');

      this.navigate(uri);
    },

    /**
    @method navigate
    @param {String} uri
    **/
    navigate: function (uri) {
      if (this.pushStateEnabled) {
        history.pushState(null, '', this.root + uri);
        this.onURIChange();
      }
      else {
        location.hash = uri;
      }
    },

    /**
    @method refresh
    **/
    refresh: function () {
      this.dispatch();
    },

    /**
    @method _attachEvents
    @protected
    **/
    _attachEvents: function () {
      var pushStateEnabled = this.pushStateEnabled,
          evt = (pushStateEnabled ? 'popstate' : 'hashchange');

      if (pushStateEnabled) {
        addEvent(window, 'popstate', this.onPopState, this);
      }
      else {
        addEvent(window, 'hashchange', this.onURIChange, this);
      }

      addEvent(document, 'click', this.onClick, this);
    },

    /**
    @method _matchesSelector
    @param {DOMNode} node
    @protected
    **/
    _matchesSelector: function (node) {
      var nodeList = document.querySelectorAll(this.linkSelector),
          contains = false,
          i;

      for ( i = 0; i < nodeList.length; i++ ) {
        if (!contains) contains = ( node === nodeList[i] );
        else break;
      }

      return contains;
    }

  };

  // -- Public API ------------------------------------------------------------
  // Only expose a tiny API to keep internal routing safe

  /**
  @singleton Aviator
  **/
  window.Aviator = {

    /**
    @property pushStateEnabled
    @type {Boolean}
    @default true if the browser supports pushState
    **/
    pushStateEnabled: ('pushState' in history),

    /**
    @property linkSelector
    @type {String}
    @default 'a.navigate'
    **/
    linkSelector: 'a.navigate',

    /**
    the root of the uri from which routing will append to

    @property root
    @type {String}
    @default ''
    **/
    root: '',

    /**
    @property _navigator
    @type {Navigator}

    @private
    **/
    _navigator: new Navigator(),

    /**
    @method setRoutes
    @param {Object} routes
    **/
    setRoutes: function (routes) {
      this._navigator.setRoutes(routes);
    },

    /**
    dispatches routes to targets and sets up event handlers

    @method dispatch
    **/
    dispatch: function () {
      var navigator = this._navigator;

      navigator.setup({
        pushStateEnabled: this.pushStateEnabled,
        linkSelector:     this.linkSelector,
        root:             this.root
      });

      navigator.dispatch();
    },

    /**
    @method navigate
    @param {String} uri to navigate to
    **/
    navigate: function (uri) {
      this._navigator.navigate(uri);
    },

    /**
    @method refresh
    **/
    refresh: function () {
      this._navigator.refresh();
    }

  };

}());
<|endoftext|>


<commit_before>"use strict";

const exec = require("../exec.js"),
	fs = require("fs"),
	path = require("path");

module.exports = (options) => {
	return _run(options);
};

function _run(options) {
	return new Promise((resolve) => {
		let config = options.config;

		if (config.repository !== null && config.repository.url !== null) {
			if (fs.existsSync(config.workspace) === true) {
				try {
					exec.local("git rev-parse --git-dir", {
						cwd: config.workspace,
					});
				} catch (err) {
					fs.rmdirSync(config.workspace);
				}

				exec.local([
					"git reset --hard",
					"git fetch --all",
					"git fetch --tags"
				].join(" && "), {
					cwd: config.workspace,
				});

				if (config.repository.branch !== null) {
					exec.local(
						`git remote set-branches --add origin ${config.repository.branch}`, {
							cwd: config.workspace,
						});

					exec.local(
						`[ -n "\`git show-ref refs/heads/${config.repository.branch}\`" ] && git checkout -q ${config.repository.branch} || git checkout -b ${config.repository.branch} origin/${config.repository.branch}`, {
						cwd: config.workspace,
					});

					exec.local(
						`git pull origin ${config.repository.branch}`, {
						cwd: config.workspace,
					});
				} else if (config.repository.tag !== null)  {
					exec.local(
						`git checkout -q refs/tags/${config.repository.tag}`, {
						cwd: config.workspace,
					});

					exec.local(
						`git pull origin ${config.repository.tag}`, {
						cwd: config.workspace,
					});
				}
			} else {
				let gitOption = "";
				if (config.repository.options.submodules === true) {
					gitOption += " --recursive";
				}

				exec.local(`git clone -b ${config.repository.branch} ${config.repository.url} --depth=1 ${config.workspace} ${gitOption}`);
			}
		} else {
			config.workspace = path.dirname(options.file);
		}

		resolve();
	}).then(() => {
		return options.config;
	});
}
<commit_msg>[WIP] implement Promise to manage init workflow (git part)<commit_after>"use strict";

const exec = require("../exec.js"),
	fs = require("fs"),
	path = require("path");

module.exports = (options) => {
	return _run(options);
};

function _run(options) {
	return new Promise((resolve) => {
		let config = options.config;

		if (config.repository !== null && config.repository.url !== null) {
			config.workspace = path.dirname(options.file);

			resolve(config);
		} else {
			Promise.resolve(options.config)
				.then(_git)
				.then((config) => {
					if (config.internal.gitClone === true) {
						Promise.resolve(config)
							.then(_gitClone)
							.then((config) => {
								resolve(config);
							});
					} else {
						Promise.resolve(config)
							.then(_gitRevParse)
							.then(_gitResetAndFetch)
							.then((config) => {
								if (config.repository.branch !== null) {
									Promise.resolve(config)
										.then(_gitRemoteSetBranches)
										.then(_gitShowRef)
										.then(_gitPullBranch)
										.then((config) => {
											resolve(config);
										});
								} else if (config.repository.tag !== null) {
									Promise.resolve(config)
										then(_gitCheckoutTag)
										then(_gitPullTag)
										.then((config) => {
											resolve(config);
										});
								}
							});
					}

					return config;
				})
				.then((config) => {
					resolve(config);
				});
		}
	}).then((config) => {
		return config;
	});
}

function _git(config) {
	return new Promise((resolve) => {
		if (fs.existsSync(config.workspace) === true) {
			config.internal.gitClone = true;
		} else {
			config.internal.gitClone = false;
		}

		resolve(config);

		resolve();
	});
}

function _gitClone(config) {
	return new Promise((resolve) => {
		let gitOption = "";
		if (config.repository.options.submodules === true) {
			gitOption += " --recursive";
		}

		exec.localAsync(`git clone -b ${config.repository.branch} ${config.repository.url} --depth=1 ${config.workspace} ${gitOption}`)
			.then(() => {
				resolve(config);
			});
	});
}

function _gitRevParse(config) {
	return new Promise((resolve, reject) => {
		exec.localAsync("git rev-parse --git-dir", {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		}).catch((error) => {
			fs.rmdirSync(config.workspace);

			reject(error);
		});
	});
}

function _gitResetAndFetch(config) {
	return new Promise((resolve) => {
		exec.localAsync([
			"git reset --hard",
			"git fetch --all",
			"git fetch --tags"
		].join(" && "), {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}

function _gitRemoteSetBranches(config) {
	return new Promise((resolve) => {
		exec.localAsync(`git remote set-branches --add origin ${config.repository.branch}`, {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}

function _gitShowRef(config) {
	return new Promise((resolve) => {
		exec.localAsync(`[ -n "\`git show-ref refs/heads/${config.repository.branch}\`" ] && git checkout -q ${config.repository.branch} || git checkout -b ${config.repository.branch} origin/${config.repository.branch}`, {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}

function _gitPullBranch(config) {
	return new Promise((resolve) => {
		exec.localAsync(`git pull origin ${config.repository.branch}`, {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}

function _gitPullTag(config) {
	return new Promise((resolve) => {
		exec.localAsync(`git pull origin ${config.repository.tag}`, {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}

function _gitCheckoutTag(config) {
	return new Promise((resolve) => {
		exec.localAsync(`git checkout -q refs/tags/${config.repository.tag}`, {
			cwd: config.workspace,
		}).then(() => {
			resolve(config);
		});
	});
}
<|endoftext|>