<commit_before>(function gc_controller_Login(l10n){
// is done under M V C pattern
Ext.define('App.um.controller.Login',{
    extend: Ext.app.Controller,

    init: function controllerLoginInit(){
    var user, role, pass, auth
       ,me = this, defer

        if(!App.um.view.Login){
            App.um.view.Login = new App.um.view.LoginWindow
            defer = 0// login
        } else {
            defer = 1// relogin
        }

        App.um.view.Login.el.select("#l10n > span").each(function l10n_changers(el){
            if(!defer){// login
                if(0 == el.dom.className.indexOf(l10n.lang)){
                    el.dom.style.opacity = 0.5// fade out current flag
                    el.dom.style.cursor = 'not-allowed'
                } else {
                    el.dom.onclick = l10n_set_and_change// install changer
                }
                return
            } else {// relogin: disable all
                el.dom.style.opacity = 0.5
                el.dom.style.cursor = 'not-allowed'
                return
            }
        })

        if(defer) return me.relogin()

        App.cfg.createViewport = false// tell `Main`, me will fire `createViewport`
        // UI refs
        user = App.um.view.Login.user
        role = App.um.view.Login.role
        pass = App.um.view.Login.pass
        auth = App.um.view.Login.auth
        // data
        App.User = App.um.model.User// must be required first from app module
        App.User.internalId = ''// reset to be used as User.id copy while offline
        // action
        user.disable()// mark to check exixting session
        user.onTriggerClick = onSessionShutdownClick
        user.focus()
        user.on({
            /* using `ref`s, but this is equivalent to:
                this.listen component:{
                     'field[name=user]':{
                        specialkey: gotoRoles
                }
             * or *
                this.control({
                    'field[name=user]':{
                        specialkey: gotoRoles
                }
             */
            specialkey: gotoRoles,
            change: reqRole
        })
        role.on({
            specialkey: gotoPass// double keypress needed
           ,change: enablePass
        })
        pass.on({
            specialkey: gotoAuth
           ,change: enableAuth
        })
        auth.on({
            click: authenticate
        })

        App.app.listen({// bind to the Application controller as this one
            global:{    // is destroyed after Login
                'initwes@UI': handleInitBackendWaitEvents,
                'usts@UI': changeUserStatus,
                'wes4UI': backendEventsCtlLogin,
                logout: logout
            }
        })

        return App.User.login('?', getSessionInfo)// ask backend for current session

        function l10n_set_and_change(){
            if('l10n-reset' == this.className){
                if(localStorage.l10n){
                    delete localStorage.l10n
                    Ext.Msg.alert({
                        icon: Ext.Msg.INFO,
                        buttons: Ext.Msg.OK,
                        title: l10n.um.l10n,
                        msg: l10n.um.l10nReset,
                        callback: reload
                    })
                }
                return
            }
            localStorage.l10n = this.className.slice(0, 2)// first two
            reload()
        }

        function getSessionInfo(err, ret){
            if(err) return

            user.enable()// mark as ready
            if(ret.can){
                user.emptyText = ret.user.id
                user.applyEmptyText()
                user.setHideTrigger(false)

                role.suspendEvents()// prevent e.g. pass.enable()
                role.setValue(l10n.um.roles[ret.can.__name] || ret.can.__name)
                role.resumeEvents()
                if('developer.local' == ret.can.__name){//!!!devel helper
                    console.log('[supro devel] fast pass for existing session')
                    authenticate()// fast pass in
                    return
                }
                auth.setText(l10n.um.loginCurrentSession)
                auth.enable()// auth is ok in this session
            }
        }

        // auth data actions
        function onSessionShutdownClick(ev){
            App.User.logout()
            ev && Ext.Msg.alert({
                icon: Ext.Msg.INFO,
                buttons: Ext.Msg.OK,
                title: l10n.um.logoutTitle,
                msg: l10n.um.logoutMsg(user.emptyText)
            })
            user.emptyText = l10n.um.loginUserBlank
            user.reset()
            user.focus()
            user.setHideTrigger(true)
            role.store.removeAll(true)
            role.reset()
            auth.disable()
            auth.setText(l10n.um.loginOk)
        }

        function reqRole(field, newUserId){
            if(defer) clearTimeout(defer)
            if(newUserId){
                if(!auth.disabled && newUserId != user.emptyText){
                    onSessionShutdownClick()
                }
                defer = setTimeout(function deferReqRoles(){
                    defer = 0
                    App.User.login(newUserId, function getSessionInfo(err, ret){
                        if(err) return

                        if(ret.can){
                            App.um.view.Login.fadeOut(createViewportAuth)
                            return// auth is ok in this session
                        }

                        if(!ret.roles.length){// no user or roles
                            if(!role.disabled){// if UI has something already
                                role.store.removeAll(true)
                                role.reset()
                                role.disable()
                                pass.reset()
                                pass.disable()
                                auth.disable()
                            }
                            return
                        }
                        var models = new Array(ret.roles.length)
                           ,i = 0, r

                        for(; i < ret.roles.length; i++){
                            r = ret.roles[i]
                            models[i] = { role: l10n.um.roles[r] || r, '=': r }
                        }
                        role.store.loadData(models, false)

                        if(role.disabled){
                            role.enable()
                        }
                    })
                }, 512)
                return
            }
            if(!role.disabled){// empty user id
                role.store.removeAll(true)
                role.reset()
                role.disable()
                pass.reset()
                pass.disable()
                auth.disable()
            }
        }
        function authenticate(field){
            // prevent session activation and stall on sudden page reload/crash
            /* NOTE: there is no way to match reload or window/tab close
             *       in the browser.
             *       Thus session is lost and relogin is required. But
             *       this can be automated by userscripts.
             *       In Chrome `Ext.EventManager.onWindowUnload()` works.
             *
             * node-webkit: session is destroyed only on window `close`
             **/
            Ext.EventManager.onWindowUnload(do_logout)// `browser`
            if(App.backendURL){// `nw`
                app.w.on('close', function nw_close(){
                    App.User.logoutUI()
                    this.close(true)
                })
            }

            if(field){// from button call arguments: `field, ev`
                App.um.view.Login.fadeInProgress(do_auth)
            } else {// from direct call
                App.cfg.extjs.fading = false
                do_auth()// fast `developer.local`
            }

            return

            function do_logout(){
                App.User.logoutUI()
            }

            function do_auth(){
                App.User.auth(
                    user.getValue(),
                    role.getValue(),
                    pass.getValue(),
                    callbackAuth
                )
            }
            function callbackAuth(err, json, res){
                if(!err){
                    return App.um.view.Login.fadeOut(createViewportAuth)
                } else {
                    App.User.logoutUI = Ext.emptyFn// prevent shutdown
                    // reload if no session (e.g. backend reloaded)
                    if(res.status && 402 === res.status) location.reload(true)
                    if(res.status && 409 === res.status){// race inside session
                        user.setHideTrigger(true)
                        user.addCls('redwhite')
                        user.disable()
                        role.disable()
                        pass.disable()
                        auth.disable()
                        auth.setText(l10n.um.loginConflict)
                    } else {// continue (e.g. wrong password)
                        user.selectText()
                    }
                    return App.um.view.Login.fadeOutProgress()
                }
            }
        }
        function createViewportAuth(){
        var bar = App.view.items_Bar, i = 0, f

            me.destroy()// Login is done

            for(i = 0; i < bar.length; ++i){// search user status item
                f = bar[i]
                if('um.usts' == f.id){
                    f.tooltip = l10n.um.userStatus + ':
' + App.User.get('id') + ''
                    f.text = (
                        '' +
                            App.User.get('name') +
                        ' (' + App.User.can.__name + ')'
                    )
                    break
                }
            }
            if(App.User.modules){// per user/role UI module setup
                if((f = App.User.modules.css)) for(i = 0; i < f.length; ++i){
                    app.extjs_helper(f[i], App.backendURL)// css loading
                }
                app.extjs_helper = null// mark for GC
                Ext.syncRequire(App.User.modules.js)
            }

            Ext.globalEvents.fireEvent('createViewport')
            //!!! do not apply shutdown for now !!!
            App.User.logoutUI = Ext.emptyFn
        }

        function gotoRoles(_, ev){
            if(ev.getKey() == ev.ENTER){
                (role.disabled ? auth : role).focus()
            }
        }
        function gotoPass(_, ev){
            if(ev.getKey() == ev.ENTER){
                pass.focus()
            }
        }
        function gotoAuth(_, ev){
            if(ev.getKey() == ev.ENTER){
                auth.focus()
            }
        }
        function enablePass(_, value){
            pass.enable()
        }
        function enableAuth(){
            auth.enable()
        }
    },
    relogin: function relogin(){
    var user, role, pass, auth
       ,me = this

        user = App.um.view.Login.user
        role = App.um.view.Login.role
        pass = App.um.view.Login.pass
        auth = App.um.view.Login.auth
        auth.enable()

        user.onTriggerClick = destroy
        user.emptyText = App.User.id.replace(/....([^ ]+) .*/,'$1')
        user.applyEmptyText()
        user.enable()
        user.setHideTrigger(false)

        role.setValue(l10n.um.roles[App.User.can.__name] || App.User.can.__name)
        role.disable()

        pass.enable()
        pass.on({
            specialkey: function gotoAuth2(_, ev){
                if(ev.getKey() == ev.ENTER){
                    auth.focus()
                }
            }
        })

        pass.setValue(' ')// crutch to activate input field:
        setTimeout(function(){
            pass.setValue('')
            pass.focus()
        }, 128)

        auth.on({ click: function(){
            App.um.view.Login.fadeInProgress(function(){
            App.User.login(App.User.get('id'), function(err, ret){
                if(err) return
            App.User.auth(
                App.User.get('id'),
                App.User.can.__name,
                pass.getValue(), function(err, json, res){
                    if(err){
                        if(res.status && 402 === res.status){
                            App.denyMsg()
                        } else if(res.status && 409 === res.status){
                            user.setHideTrigger(false)
                            role.disable()
                            pass.disable()
                            auth.disable()
                            auth.setText(l10n.um.loginConflict)
                        }
                        return App.um.view.Login.fadeOutProgress()
                    }

            App.um.wes(App.User.internalId.slice(0,4))// saved status
            return App.um.view.Login.fadeOut(destroy)
                }
            )})})
        }})
        return

        function destroy(){
            me.destroy()
        }
    },
    destroy: function destroy(){
    /*
     * is not used any more
     * NOTE: this controller is handling events `me.listen()`
     * TODO: in case of logout event from backend, this may show
     *       `App.um.view.Login` again without reloading of all Viewport
     */
        //App.um.view.Login.destroy()
        App.um.view.Login.destroy()
        App.um.view.Login = null// GC
        this.application.eventbus.unlisten(this.id)
        this.application.controllers.removeAtKey(this.id)
    }
})

/*
 * Application wide event handlers
 **/
function backendEventsCtlLogin(success, data){
var evn, cmp, s

    App.sts(
       'backend events',
        success ? data.length : data,// data || res.statusText
        success ? l10n.stsOK : l10n.stsHE,
        new Date
    )
    console.log(data)//TODO: Ext,Msg on error
    console.table(data)

    if('string' == typeof data) switch (data){// simple event
        case 'Disconnect':
        case 'Unauthorized':
        case 'Payment Required':
            if(App.um.view.Login) return// event is firing again (still no login)

            (cmp = Ext.getCmp('um.usts')) && cmp.setIconCls('appbar-user-offl')
            App.um.view.Login = new App.um.view.LoginWindow({ modal: true })
            Ext.WindowManager.bringToFront(App.um.view.Login)
            App.app.getController('App.um.controller.Login')// only strings
        return
        default:return
    }

    for(evn = 0; evn < data.length; ++evn) switch (data[evn].ev){
        case 'Usts@um': if((cmp = Ext.getCmp('um.usts'))){
            if(cmp.iconCls.slice(12) != (s = data[evn].json.slice(0, 4))){
                cmp.setIconCls('appbar-user-' + s)
            }
        }
        break
        case 'Disconnect':
            (cmp = Ext.getCmp('um.usts')) && cmp.setIconCls('appbar-user-offl')
        break
        case 'Unauthorized':

            App.um.view.Login = new App.um.view.LoginWindow({ modal: true })
            Ext.WindowManager.bringToFront(App.um.view.Login)
            App.app.getController(App.um.controller.Login)

        break
        default:break
    }
}

function handleInitBackendWaitEvents(msg){
    App.sts(
        msg,
       'init backend Wait EventS',
        l10n.stsOK,
        new Date
    )
}

function changeUserStatus(status){
    App.um.wes(status)
}

function logout(){
    App.app.suspendEvents(false)

    Ext.Msg.alert({
        buttons: Ext.Msg.YESNO,
        icon: Ext.Msg.QUESTION,
        title: l10n.um.logoutTitle,
        msg: l10n.um.logoutMsg(
            App.um.model.User.get('id'),
            l10n.um.roles[App.User.can.__name] || App.User.can.__name
        ),
        fn: function(btn){
            if('yes' == btn) App.User.logout(reload)
        }
    })
}

function reload(){
    location.reload(true)
}

App.denyMsg = function denyMsg(){
    Ext.Msg.alert({
        icon: Ext.Msg.WARNING,
        buttons: Ext.Msg.OK,
        title: l10n.um.auth,
        msg: l10n.um.deny,
        fn: function(){ }
    })
}

App.app.getController('App.um.controller.Login')/* init */

})(l10n)
<commit_msg>um.ctl.Login: open code doubly deprecated inefficient `store.loadData()`<commit_after>(function gc_controller_Login(l10n){
// is done under M V C pattern
Ext.define('App.um.controller.Login',{
    extend: Ext.app.Controller,

    init: function controllerLoginInit(){
    var user, role, pass, auth
       ,me = this, defer

        if(!App.um.view.Login){
            App.um.view.Login = new App.um.view.LoginWindow
            defer = 0// login
        } else {
            defer = 1// relogin
        }

        App.um.view.Login.el.select("#l10n > span").each(function l10n_changers(el){
            if(!defer){// login
                if(0 == el.dom.className.indexOf(l10n.lang)){
                    el.dom.style.opacity = 0.5// fade out current flag
                    el.dom.style.cursor = 'not-allowed'
                } else {
                    el.dom.onclick = l10n_set_and_change// install changer
                }
                return
            } else {// relogin: disable all
                el.dom.style.opacity = 0.5
                el.dom.style.cursor = 'not-allowed'
                return
            }
        })

        if(defer) return me.relogin()

        App.cfg.createViewport = false// tell `Main`, me will fire `createViewport`
        // UI refs
        user = App.um.view.Login.user
        role = App.um.view.Login.role
        pass = App.um.view.Login.pass
        auth = App.um.view.Login.auth
        // data
        App.User = App.um.model.User// must be required first from app module
        App.User.internalId = ''// reset to be used as User.id copy while offline
        // action
        user.disable()// mark to check exixting session
        user.onTriggerClick = onSessionShutdownClick
        user.focus()
        user.on({
            /* using `ref`s, but this is equivalent to:
                this.listen component:{
                     'field[name=user]':{
                        specialkey: gotoRoles
                }
             * or *
                this.control({
                    'field[name=user]':{
                        specialkey: gotoRoles
                }
             */
            specialkey: gotoRoles,
            change: reqRole
        })
        role.on({
            specialkey: gotoPass// double keypress needed
           ,change: enablePass
        })
        pass.on({
            specialkey: gotoAuth
           ,change: enableAuth
        })
        auth.on({
            click: authenticate
        })

        App.app.listen({// bind to the Application controller as this one
            global:{    // is destroyed after Login
                'initwes@UI': handleInitBackendWaitEvents,
                'usts@UI': changeUserStatus,
                'wes4UI': backendEventsCtlLogin,
                logout: logout
            }
        })

        return App.User.login('?', getSessionInfo)// ask backend for current session

        function l10n_set_and_change(){
            if('l10n-reset' == this.className){
                if(localStorage.l10n){
                    delete localStorage.l10n
                    Ext.Msg.alert({
                        icon: Ext.Msg.INFO,
                        buttons: Ext.Msg.OK,
                        title: l10n.um.l10n,
                        msg: l10n.um.l10nReset,
                        callback: reload
                    })
                }
                return
            }
            localStorage.l10n = this.className.slice(0, 2)// first two
            reload()
        }

        function getSessionInfo(err, ret){
            if(err) return

            user.enable()// mark as ready
            if(ret.can){
                user.emptyText = ret.user.id
                user.applyEmptyText()
                user.setHideTrigger(false)

                role.suspendEvents()// prevent e.g. pass.enable()
                role.setValue(l10n.um.roles[ret.can.__name] || ret.can.__name)
                role.resumeEvents()
                if('developer.local' == ret.can.__name){//!!!devel helper
                    console.log('[supro devel] fast pass for existing session')
                    authenticate()// fast pass in
                    return
                }
                auth.setText(l10n.um.loginCurrentSession)
                auth.enable()// auth is ok in this session
            }
        }

        // auth data actions
        function onSessionShutdownClick(ev){
            App.User.logout()
            ev && Ext.Msg.alert({
                icon: Ext.Msg.INFO,
                buttons: Ext.Msg.OK,
                title: l10n.um.logoutTitle,
                msg: l10n.um.logoutMsg(user.emptyText)
            })
            user.emptyText = l10n.um.loginUserBlank
            user.reset()
            user.focus()
            user.setHideTrigger(true)
            role.store.removeAll(true)
            role.reset()
            auth.disable()
            auth.setText(l10n.um.loginOk)
        }

        function reqRole(field, newUserId){
            if(defer) clearTimeout(defer)
            if(newUserId){
                if(!auth.disabled && newUserId != user.emptyText){
                    onSessionShutdownClick()
                }
                defer = setTimeout(function deferReqRoles(){
                    defer = 0
                    App.User.login(newUserId, function getSessionInfo(err, ret){
                        if(err) return

                        if(ret.can){
                            App.um.view.Login.fadeOut(createViewportAuth)
                            return// auth is ok in this session
                        }

                        if(!ret.roles.length){// no user or roles
                            if(!role.disabled){// if UI has something already
                                role.store.removeAll(true)
                                role.reset()
                                role.disable()
                                pass.reset()
                                pass.disable()
                                auth.disable()
                            }
                            return
                        }
                        var models = new Array(ret.roles.length)
                           ,Role = role.store.model
                           ,i = 0, r

                        for(; i < ret.roles.length; i++){
                            r = ret.roles[i]
                            models[i] = new Role({
                                role: l10n.um.roles[r] || r,
                                '=': r
                            })
                        }
                        role.store.loadRecords(models, false)

                        if(role.disabled){
                            role.enable()
                        }
                    })
                }, 512)
                return
            }
            if(!role.disabled){// empty user id
                role.store.removeAll(true)
                role.reset()
                role.disable()
                pass.reset()
                pass.disable()
                auth.disable()
            }
        }
        function authenticate(field){
            // prevent session activation and stall on sudden page reload/crash
            /* NOTE: there is no way to match reload or window/tab close
             *       in the browser.
             *       Thus session is lost and relogin is required. But
             *       this can be automated by userscripts.
             *       In Chrome `Ext.EventManager.onWindowUnload()` works.
             *
             * node-webkit: session is destroyed only on window `close`
             **/
            Ext.EventManager.onWindowUnload(do_logout)// `browser`
            if(App.backendURL){// `nw`
                app.w.on('close', function nw_close(){
                    App.User.logoutUI()
                    this.close(true)
                })
            }

            if(field){// from button call arguments: `field, ev`
                App.um.view.Login.fadeInProgress(do_auth)
            } else {// from direct call
                App.cfg.extjs.fading = false
                do_auth()// fast `developer.local`
            }

            return

            function do_logout(){
                App.User.logoutUI()
            }

            function do_auth(){
                App.User.auth(
                    user.getValue(),
                    role.getValue(),
                    pass.getValue(),
                    callbackAuth
                )
            }
            function callbackAuth(err, json, res){
                if(!err){
                    return App.um.view.Login.fadeOut(createViewportAuth)
                } else {
                    App.User.logoutUI = Ext.emptyFn// prevent shutdown
                    // reload if no session (e.g. backend reloaded)
                    if(res.status && 402 === res.status) location.reload(true)
                    if(res.status && 409 === res.status){// race inside session
                        user.setHideTrigger(true)
                        user.addCls('redwhite')
                        user.disable()
                        role.disable()
                        pass.disable()
                        auth.disable()
                        auth.setText(l10n.um.loginConflict)
                    } else {// continue (e.g. wrong password)
                        user.selectText()
                    }
                    return App.um.view.Login.fadeOutProgress()
                }
            }
        }
        function createViewportAuth(){
        var bar = App.view.items_Bar, i = 0, f

            me.destroy()// Login is done

            for(i = 0; i < bar.length; ++i){// search user status item
                f = bar[i]
                if('um.usts' == f.id){
                    f.tooltip = l10n.um.userStatus + ':
' + App.User.get('id') + ''
                    f.text = (
                        '' +
                            App.User.get('name') +
                        ' (' + App.User.can.__name + ')'
                    )
                    break
                }
            }
            if(App.User.modules){// per user/role UI module setup
                if((f = App.User.modules.css)) for(i = 0; i < f.length; ++i){
                    app.extjs_helper(f[i], App.backendURL)// css loading
                }
                app.extjs_helper = null// mark for GC
                Ext.syncRequire(App.User.modules.js)
            }

            Ext.globalEvents.fireEvent('createViewport')
            //!!! do not apply shutdown for now !!!
            App.User.logoutUI = Ext.emptyFn
        }

        function gotoRoles(_, ev){
            if(ev.getKey() == ev.ENTER){
                (role.disabled ? auth : role).focus()
            }
        }
        function gotoPass(_, ev){
            if(ev.getKey() == ev.ENTER){
                pass.focus()
            }
        }
        function gotoAuth(_, ev){
            if(ev.getKey() == ev.ENTER){
                auth.focus()
            }
        }
        function enablePass(_, value){
            pass.enable()
        }
        function enableAuth(){
            auth.enable()
        }
    },
    relogin: function relogin(){
    var user, role, pass, auth
       ,me = this

        user = App.um.view.Login.user
        role = App.um.view.Login.role
        pass = App.um.view.Login.pass
        auth = App.um.view.Login.auth
        auth.enable()

        user.onTriggerClick = destroy
        user.emptyText = App.User.id.replace(/....([^ ]+) .*/,'$1')
        user.applyEmptyText()
        user.enable()
        user.setHideTrigger(false)

        role.setValue(l10n.um.roles[App.User.can.__name] || App.User.can.__name)
        role.disable()

        pass.enable()
        pass.on({
            specialkey: function gotoAuth2(_, ev){
                if(ev.getKey() == ev.ENTER){
                    auth.focus()
                }
            }
        })

        pass.setValue(' ')// crutch to activate input field:
        setTimeout(function(){
            pass.setValue('')
            pass.focus()
        }, 128)

        auth.on({ click: function(){
            App.um.view.Login.fadeInProgress(function(){
            App.User.login(App.User.get('id'), function(err, ret){
                if(err) return
            App.User.auth(
                App.User.get('id'),
                App.User.can.__name,
                pass.getValue(), function(err, json, res){
                    if(err){
                        if(res.status && 402 === res.status){
                            App.denyMsg()
                        } else if(res.status && 409 === res.status){
                            user.setHideTrigger(false)
                            role.disable()
                            pass.disable()
                            auth.disable()
                            auth.setText(l10n.um.loginConflict)
                        }
                        return App.um.view.Login.fadeOutProgress()
                    }

            App.um.wes(App.User.internalId.slice(0,4))// saved status
            return App.um.view.Login.fadeOut(destroy)
                }
            )})})
        }})
        return

        function destroy(){
            me.destroy()
        }
    },
    destroy: function destroy(){
    /*
     * is not used any more
     * NOTE: this controller is handling events `me.listen()`
     * TODO: in case of logout event from backend, this may show
     *       `App.um.view.Login` again without reloading of all Viewport
     */
        //App.um.view.Login.destroy()
        App.um.view.Login.destroy()
        App.um.view.Login = null// GC
        this.application.eventbus.unlisten(this.id)
        this.application.controllers.removeAtKey(this.id)
    }
})

/*
 * Application wide event handlers
 **/
function backendEventsCtlLogin(success, data){
var evn, cmp, s

    App.sts(
       'backend events',
        success ? data.length : data,// data || res.statusText
        success ? l10n.stsOK : l10n.stsHE,
        new Date
    )
    console.log(data)//TODO: Ext,Msg on error
    console.table(data)

    if('string' == typeof data) switch (data){// simple event
        case 'Disconnect':
        case 'Unauthorized':
        case 'Payment Required':
            if(App.um.view.Login) return// event is firing again (still no login)

            (cmp = Ext.getCmp('um.usts')) && cmp.setIconCls('appbar-user-offl')
            App.um.view.Login = new App.um.view.LoginWindow({ modal: true })
            Ext.WindowManager.bringToFront(App.um.view.Login)
            App.app.getController('App.um.controller.Login')// only strings
        return
        default:return
    }

    for(evn = 0; evn < data.length; ++evn) switch (data[evn].ev){
        case 'Usts@um': if((cmp = Ext.getCmp('um.usts'))){
            if(cmp.iconCls.slice(12) != (s = data[evn].json.slice(0, 4))){
                cmp.setIconCls('appbar-user-' + s)
            }
        }
        break
        case 'Disconnect':
            (cmp = Ext.getCmp('um.usts')) && cmp.setIconCls('appbar-user-offl')
        break
        case 'Unauthorized':

            App.um.view.Login = new App.um.view.LoginWindow({ modal: true })
            Ext.WindowManager.bringToFront(App.um.view.Login)
            App.app.getController(App.um.controller.Login)

        break
        default:break
    }
}

function handleInitBackendWaitEvents(msg){
    App.sts(
        msg,
       'init backend Wait EventS',
        l10n.stsOK,
        new Date
    )
}

function changeUserStatus(status){
    App.um.wes(status)
}

function logout(){
    App.app.suspendEvents(false)

    Ext.Msg.alert({
        buttons: Ext.Msg.YESNO,
        icon: Ext.Msg.QUESTION,
        title: l10n.um.logoutTitle,
        msg: l10n.um.logoutMsg(
            App.um.model.User.get('id'),
            l10n.um.roles[App.User.can.__name] || App.User.can.__name
        ),
        fn: function(btn){
            if('yes' == btn) App.User.logout(reload)
        }
    })
}

function reload(){
    location.reload(true)
}

App.denyMsg = function denyMsg(){
    Ext.Msg.alert({
        icon: Ext.Msg.WARNING,
        buttons: Ext.Msg.OK,
        title: l10n.um.auth,
        msg: l10n.um.deny,
        fn: function(){ }
    })
}

App.app.getController('App.um.controller.Login')/* init */

})(l10n)
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/* global cordova:false */
/* globals window */

var exec = cordova.require('cordova/exec'),
    utils = cordova.require('cordova/utils');

/**
    @description A global object that lets you interact with the Creative SDK Asset Browser.
    @global
*/
var CSDKAssetBrowser = {
    /** @private */
    getFileMetadata: function(successCallback, failureCallback) {
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'getFileMetadata', []);
    },

    /**
     * @description Downloads a file from the Creative Cloud.
     * @function downloadFiles
     * @memberof CSDKAssetBrowser
     * @param {!successCallback} successCallback - See type definition.
     * @param {!errorCallback} errorCallback - See type definition.
     * @param {?DownloadOptions} options An object containing optional property/value pairs.
     */
    downloadFiles: function(successCallback, failureCallback, options) {
        options = options || {};
        options.dataSource = CSDKAssetBrowser.DataSourceType.FILES;
        var dataSourceTypes = CSDKAssetBrowser.getDataSources(options.dataSource);
        var outputFile = options.outputFile || '';
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'downloadFiles', [ dataSourceTypes, outputFile ]);
    },

    /**
     * @description Uploads a file to the Creative Cloud.
     * @function uploadFile
     * @memberof CSDKAssetBrowser
     * @param {!successCallback} successCallback - See type definition.
     * @param {!errorCallback} errorCallback - See type definition.
     * @param {!string} url path to the asset to be uploaded.
     * @param {?UploadOptions} options An object containing optional property/value pairs.
     */
    uploadFile: function(successCallback, failureCallback, url, options) {
        options = options || {};
        var uploadName = options.uploadName || '';
        var overwrite = !!options.overwrite;
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'uploadFile', [ url, uploadName, overwrite ]);
    },

    /** @private */
    getDataSources: function(types) {
        var validTypes = [];
        if (types) {
            for(var i=0; i<types.length; i++) {
                if (types[i] >= 0 && types[i] <= 8) {
                    validTypes.push(types[i]);
                }
            }
        }
        return validTypes;
    },

    /**
     * @readonly
     * @enum {number}
     */
    DataSourceType:{
        COMPOSITIONS: 0,
        DRAW: 1,
        FILES: 2,
        LIBRARY: 3,
        PHOTOS: 4,
        PSMIX: 5,
        SKETCHES: 6,
        LINE: 7,
        BRUSH: 8
    }
};

/**
 * @description A callback to be used upon successful upload or download of an image.
 *
 * @callback successCallback
 * @param {string} newUrl - The URL of the new downloaded image.
 */

/**
 * @description A callback to handle errors when attempting to upload or download an image.
 *
 * @callback errorCallback
 * @param {Object} error - Error object.
 */

/**
 * @typedef {Object} DownloadOptions - An object for configuring Asset Browser download behavior.
 * @property {string} [outputFile=''] - Path to save the file. If not specified the system default is used.
 */

/**
 * @typedef {Object} UploadOptions - An object for configuring Asset Browser upload behavior.
 * @property {string} [uploadName=''] - The name your want the file to have in the Creative Cloud. If not specified the current file name is used.
 * @property {boolean} [overwrite=false] - Sets whether or not to overwrite the existing file or create a copy.
 */

module.exports = CSDKAssetBrowser;
<commit_msg>dataSource should be array of ints.<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/* global cordova:false */
/* globals window */

var exec = cordova.require('cordova/exec'),
    utils = cordova.require('cordova/utils');

/**
    @description A global object that lets you interact with the Creative SDK Asset Browser.
    @global
*/
var CSDKAssetBrowser = {
    /** @private */
    getFileMetadata: function(successCallback, failureCallback) {
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'getFileMetadata', []);
    },

    /**
     * @description Downloads a file from the Creative Cloud.
     * @function downloadFiles
     * @memberof CSDKAssetBrowser
     * @param {!successCallback} successCallback - See type definition.
     * @param {!errorCallback} errorCallback - See type definition.
     * @param {?DownloadOptions} options An object containing optional property/value pairs.
     */
    downloadFiles: function(successCallback, failureCallback, options) {
        options = options || {};
        options.dataSource = [CSDKAssetBrowser.DataSourceType.FILES];
        var dataSourceTypes = CSDKAssetBrowser.getDataSources(options.dataSource);
        var outputFile = options.outputFile || '';
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'downloadFiles', [ dataSourceTypes, outputFile ]);
    },

    /**
     * @description Uploads a file to the Creative Cloud.
     * @function uploadFile
     * @memberof CSDKAssetBrowser
     * @param {!successCallback} successCallback - See type definition.
     * @param {!errorCallback} errorCallback - See type definition.
     * @param {!string} url path to the asset to be uploaded.
     * @param {?UploadOptions} options An object containing optional property/value pairs.
     */
    uploadFile: function(successCallback, failureCallback, url, options) {
        options = options || {};
        var uploadName = options.uploadName || '';
        var overwrite = !!options.overwrite;
        exec(successCallback, failureCallback, 'CSDKAssetBrowser', 'uploadFile', [ url, uploadName, overwrite ]);
    },

    /** @private */
    getDataSources: function(types) {
        var validTypes = [];
        if (types) {
            for(var i=0; i<types.length; i++) {
                if (types[i] >= 0 && types[i] <= 8) {
                    validTypes.push(types[i]);
                }
            }
        }
        return validTypes;
    },

    /**
     * @readonly
     * @enum {number}
     */
    DataSourceType:{
        COMPOSITIONS: 0,
        DRAW: 1,
        FILES: 2,
        LIBRARY: 3,
        PHOTOS: 4,
        PSMIX: 5,
        SKETCHES: 6,
        LINE: 7,
        BRUSH: 8
    }
};

/**
 * @description A callback to be used upon successful upload or download of an image.
 *
 * @callback successCallback
 * @param {string} newUrl - The URL of the new downloaded image.
 */

/**
 * @description A callback to handle errors when attempting to upload or download an image.
 *
 * @callback errorCallback
 * @param {Object} error - Error object.
 */

/**
 * @typedef {Object} DownloadOptions - An object for configuring Asset Browser download behavior.
 * @property {string} [outputFile=''] - Path to save the file. If not specified the system default is used.
 */

/**
 * @typedef {Object} UploadOptions - An object for configuring Asset Browser upload behavior.
 * @property {string} [uploadName=''] - The name your want the file to have in the Creative Cloud. If not specified the current file name is used.
 * @property {boolean} [overwrite=false] - Sets whether or not to overwrite the existing file or create a copy.
 */

module.exports = CSDKAssetBrowser;
<|endoftext|>


<commit_before>(function() {
  // Adding a trailing space for ease of combining with other selectors.
  var TOP_SELECTOR = '#new-integration ';

  var Finalization = finalizationFactory('finalize-integration');
  var RecipientListChoice = filteredChoicePanelFactory({
    id: 'choice-recipient-list',
    configuredServiceId: function() {
      return GmailAccountChoice.val();
    },
    nextElement: Finalization
  });
  var GmailAccountChoice = choicePanelFactory({
    id: 'choice-gmail-account',
    type: 'radio',
    nextElement: RecipientListChoice
  });
  var GmailChoices = choiceListFactory({
    id: 'choices-gmail',
    children: [GmailAccountChoice, RecipientListChoice]
  });
  var DataDestinationServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-destination-service',
    serviceId: 'service-gmail',
    nextChoices: GmailChoices
  });
  var DataDestinationChoices = choiceListFactory({
    id: 'choices-data-destination',
    children: [DataDestinationServiceChoice, GmailChoices]
  });
  var MessageChoice = choicePanelFactory({
    id: 'choice-message',
    nextElement: DataDestinationChoices,
    hidden: true
  });
  var PatternChoice = choicePanelFactory({
    id: 'choice-pattern',
    nextElement: MessageChoice
  });
  var AlertChoices = choiceListFactory({
    id: 'choices-alert',
    children: [PatternChoice, MessageChoice]
  });
  var FormChoice = filteredChoicePanelFactory({
    id: 'choice-form',
    configuredServiceId: function() {
      return ServerChoice.val();
    },
    nextElement: AlertChoices
  });
  var ServerChoice = choicePanelFactory({
    id: 'choice-server',
    type: 'radio',
    nextElement: FormChoice
  });
  var ODKChoices = choiceListFactory({
    id: 'choices-odk',
    children: [ServerChoice, FormChoice]
  });
  var DataSourceServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-source-service',
    serviceId: 'service-odk',
    nextChoices: ODKChoices
  });
  var DataSourceChoices = choiceListFactory({
    id: 'choices-data-source',
    children: [DataSourceServiceChoice, ODKChoices]
  });
  var Draft = draftFactory({ firstElement: DataSourceChoices });
  var CongenialEnigma = listenerFactory([
    DataSourceChoices,
    AlertChoices,
    DataDestinationChoices,
    Draft
  ]);
  CongenialEnigma.listen();
  return;

  /* ------------------------------------------------------------------------ */
                    /* UI helper functions */
  /* ------------------------------------------------------------------------ */

  // Activates a step title (for example, "1 Choose a Data Source") and
  // deactivates the others. $descendant is an element nested under the step.
  function activateStepTitle($descendant) {
    var $h4 = $descendant.closest('tr').find('h4');
    if ($h4.hasClass('text-muted')) {
      $(TOP_SELECTOR + '> tbody > tr h4').addClass('text-muted');
      $h4.removeClass('text-muted');
    }
  }

  /* ------------------------------------------------------------------------ */
                    /* component factories */
  /* ------------------------------------------------------------------------ */

  // Returns an object that listens by calling other listeners.
  function listenerFactory(listeners) {
    return {
      listen: function() {
        listeners.forEach(function(listener) {
          listener.listen();
        });
      }
    };
  }

  // nextChoices must be a choice list.
  function serviceChoiceFactory(options) {
    return {
      nextElement: function() {
        return options.nextChoices;
      },
      hasDraft: function() {
        return options.nextChoices.children[0].hasDraft();
      },
      restoreDraft: function() {
        var draft = this.hasDraft();
        if (draft)
          options.nextChoices.show();
        return draft;
      },
      listen: function() {
        // Adding a trailing space for ease of combining with other selectors.
        var choiceSelector = TOP_SELECTOR + '#' + options.choiceId + ' ';
        var serviceSelector = TOP_SELECTOR + '#' + options.serviceId + ' ';
        var otherServicesSelector = choiceSelector +
          '.service:not(#' + options.serviceId + ') ';
        $(document)
          .on('click', serviceSelector, function(e) {
            e.preventDefault();
            $(otherServicesSelector).fadeTo('slow', 0, function() {
              $(this).remove();
              options.nextChoices.show();
            });
            $(serviceSelector).fadeTo('slow', 1);
          })
          .on('click', otherServicesSelector, function(e) {
            e.preventDefault();
            options.nextChoices.hide();
            activateStepTitle($(choiceSelector));
            var id = '#' + $(this).attr('id');
            $(choiceSelector + '.service:not(' + id + ')').fadeTo('fast', 0.5);
            $(id).fadeTo('fast', 1);
          });
      }
    };
  }

  /*
  choiceListFactory() returns a "choice list," which groups together one or more
  other choices and/or choice lists. Think of a choice list as a tree, where
  choice lists are branches and choices are leaves. Options:

    id        HTML id of the choice list element
    children  Array of the choices and choice lists contained in the list
              (immediate children only)
  */
  function choiceListFactory(options) {
    var lastChild = options.children[options.children.length - 1];
    var $choices, hidden;
    return {
      children: options.children,
      nextElement: function() {
        return lastChild.nextElement();
      },
      state: function() {
        if (hidden)
          return 'hidden';
        return lastChild.state() === 'complete' ? 'complete' : 'active';
      },
      hasDraft: function() {
        return options.children[0].hasDraft();
      },
      restoreDraft: function() {
        for (var i = 0; i < options.children.length; i++) {
          if (!options.children[i].restoreDraft())
            return false;
        }
        return true;
      },
      show: function() {
        $choices.show();
        hidden = false;
        if (this.state() === 'complete')
          this.nextElement().show();
        else
          activateStepTitle($choices);
        hidden = false;
      },
      hide: function() {
        this.nextElement().hide();
        $choices.hide();
        hidden = true;
      },
      listen: function() {
        $(document).on('turbolinks:load', function() {
          $choices = $('#' + options.id);
          hidden = true;
        });
        options.children.forEach(function(child) {
          child.listen();
        });
      }
    };
  }

  function choicePanelFactory(options) {
    var $panel, state;
    var choice = {
      nextElement: nextElement,
      state: state,
      hasDraft: hasDraft,
      restoreDraft: restoreDraft,
      complete: complete,
      revisit: revisit,
      listen: listen
    };
    if (options.type === 'radio')
      choice.val = valRadio;
    if (options.hidden) {
      choice.show = show;
      choice.hide = hide;
    }
    return choice;

    function nextElement() {
      return options.nextElement;
    }

    function state() {
      return state;
    }

    function hasDraft() {
      return $panel.data('hasDraft');
    }

    function restoreDraft() {
      var draft = hasDraft();
      if (draft)
        complete(true);
      return draft;
    }

    // Activates select radio buttons:
    //
    //   1. Shows radio buttons except those with a `filtered` class
    //   2. Selects/checks the first visible, enabled radio button
    //
    function activateRadio() {
      $panel.find('.radio.filtered').hide();
      var $visibleRadio = $panel.find('.radio:not(.filtered)');
      var $enabledRadio = $visibleRadio
                             .find('input:not(.permanently-disabled)')
                             .closest('.radio');
      if ($enabledRadio.length > 0) {
        $enabledRadio.removeClass('disabled');
        if ($enabledRadio.find(':checked').length === 0)
          $enabledRadio.find('input').first().prop('checked', true);
        $panel.find('.action-complete').show();
      }
      $visibleRadio.show();
    }

    /*
    Activates the panel by making the following changes:

      1. Invokes the beforeActivate callback
      2. Activates the step title under which the panel is nested
      3. Unmutes text, including the title
      4. Enables inputs except those with a permanently-disabled class
      5. Activates select radio buttons
      6. Hides the revisit action, shows the add action, and shows the complete
         action if appropriate
    */
    function activate() {
      if (state === 'active')
        return;
      if (options.beforeActivate)
        options.beforeActivate($panel);

      activateStepTitle($panel);
      $panel.removeClass('text-muted');
      $panel.find('.panel-title span').removeClass('text-muted');
      var $input = $panel.find('input, select, textarea');
      $input.filter(':not(.permanently-disabled)').prop('disabled', false);
      if ($panel.find('form:not(.form-paragraph)').length > 0)
        activateRadio();
      else
        $panel.find('.action-complete').show();
      $panel.find('.action-add').show();
      $panel.find('.action-revisit').hide();
    }

    function show() {
      if (state !== 'hidden')
        return;
      activate();
      $panel.show();
      state = 'active';
    }

    function hide() {
      if (state === 'hidden')
        return;
      nextElement().hide();
      $panel.hide();
      state = 'hidden';
    }

    function saveDraft() {
      var data = {};
      var selector = 'input[type="radio"]:checked, input[type!="radio"], ' +
        'select, textarea';
      $panel.find(selector).each(function() {
        var $el = $(this);
        var name = $el.data('draftName') || $el.attr('name');
        data[name] = $el.val();
      });
      $.post('/integrations/save_draft', data);
    }

    /*
    Updates the panel's UI after it has been completed:

      1. Mutes text, including the title
      2. Disables inputs
      3. Hides unchecked radio buttons
      4. Hides the add and complete actions and shows the revisit action
    */
    function updateUIAfterComplete() {
      $panel.addClass('text-muted');
      $panel.find('.panel-title span').addClass('text-muted');
      $panel.find('input[type="radio"]:not(:checked)').closest('.radio').hide();
      $panel.find('input, select, textarea').prop('disabled', true);
      $panel.find(':checked').closest('.radio').addClass('disabled');
      $panel.find('.action-add, .action-complete').hide();
      $panel.find('.action-revisit').show();
    }

    function complete(skipDraftSave) {
      if (state !== 'active')
        return;
      if (!skipDraftSave)
        saveDraft();
      updateUIAfterComplete();
      state = 'complete';
      nextElement().show();
    }

    function valRadio() {
      return $panel.find(':checked').val();
    }

    function revisit() {
      if (state !== 'complete')
        return;
      nextElement().hide();
      activate();
      state = 'active';
    }

    function listen() {
      // Adding a trailing space for ease of combining with other selectors.
      var selector = TOP_SELECTOR + '#' + options.id + ' ';
      $(document)
        .on('turbolinks:load', function() {
          $panel = $(selector);
          state = options.hidden ? 'hidden' : 'active';
        })
        .on('submit', selector, function(e) {
          e.preventDefault();
          complete();
        })
        .on('click', selector + '.action-revisit', function(e) {
          e.preventDefault();
          revisit();
        });
    }
  }

  function filteredChoicePanelFactory(options) {
    var filteredOptions = {
      type: 'radio',
      hidden: true,
      beforeActivate: beforeActivate
    };
    var combinedOptions = $.extend({}, options, filteredOptions);
    return choicePanelFactory(combinedOptions);

    function updateAddPath($choice) {
      var $addAction = $choice.find('.action-add');
      var path = $addAction
                   .data('path')
                   .replace('0', options.configuredServiceId());
      $addAction.find('a').prop('href', path);
    }

    function filterRadio($choice) {
      var $radio = $choice.find('.radio');
      $radio.addClass('filtered');
      var selector = '[data-configured-service-id="' +
        options.configuredServiceId() + '"]';
      $radio.filter(selector).removeClass('filtered');
    }

    function beforeActivate($choice) {
      updateAddPath($choice);
      filterRadio($choice);
    }
  }

  function finalizationFactory(id) {
    var selector = '#' + id;
    return {
      show: show,
      hide: hide
    };

    function updatePushUrl() {
      var $pushUrl = $('#push-url');
      var url = $pushUrl
                  .data('url')
                  .replace('/0', '/' + ServerChoice.val())
                  .replace('/1', '/' + FormChoice.val());
      $pushUrl.text(url);
    }

    function copyFields() {
      $('#data_source_id').val(FormChoice.val());
      $('#rule_data_field_name').val($('#field_name').val());
      $('#rule_type').val($('#rule_class').val());
      $('#rule_data_value').val($('#rule_value').val());
      $('#message').val($('#alert_message').val());
      $('#data_destination_id').val(RecipientListChoice.val());
    }

    function show() {
      updatePushUrl();
      copyFields();
      activateStepTitle($('#choices-data-destination'));
      $(selector).show();
    }

    function hide() {
      $(selector).hide();
    }
  }

  function draftFactory(options) {
    return {
      listen: function() {
        $(document).on('turbolinks:load', function() {
          for (var element = options.firstElement;
               element && element.restoreDraft && element.restoreDraft();
               element = element.nextElement())
            ;
        });
      }
    };
  }
})();
<commit_msg>Reorganize component factories<commit_after>(function() {
  // Adding a trailing space for ease of combining with other selectors.
  var TOP_SELECTOR = '#new-integration ';

  var Finalization = finalizationFactory('finalize-integration');
  var RecipientListChoice = filteredChoicePanelFactory({
    id: 'choice-recipient-list',
    configuredServiceId: function() {
      return GmailAccountChoice.val();
    },
    nextElement: Finalization
  });
  var GmailAccountChoice = choicePanelFactory({
    id: 'choice-gmail-account',
    type: 'radio',
    nextElement: RecipientListChoice
  });
  var GmailChoices = choiceListFactory({
    id: 'choices-gmail',
    children: [GmailAccountChoice, RecipientListChoice]
  });
  var DataDestinationServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-destination-service',
    serviceId: 'service-gmail',
    nextChoices: GmailChoices
  });
  var DataDestinationChoices = choiceListFactory({
    id: 'choices-data-destination',
    children: [DataDestinationServiceChoice, GmailChoices]
  });
  var MessageChoice = choicePanelFactory({
    id: 'choice-message',
    nextElement: DataDestinationChoices,
    hidden: true
  });
  var PatternChoice = choicePanelFactory({
    id: 'choice-pattern',
    nextElement: MessageChoice
  });
  var AlertChoices = choiceListFactory({
    id: 'choices-alert',
    children: [PatternChoice, MessageChoice]
  });
  var FormChoice = filteredChoicePanelFactory({
    id: 'choice-form',
    configuredServiceId: function() {
      return ServerChoice.val();
    },
    nextElement: AlertChoices
  });
  var ServerChoice = choicePanelFactory({
    id: 'choice-server',
    type: 'radio',
    nextElement: FormChoice
  });
  var ODKChoices = choiceListFactory({
    id: 'choices-odk',
    children: [ServerChoice, FormChoice]
  });
  var DataSourceServiceChoice = serviceChoiceFactory({
    choiceId: 'choice-data-source-service',
    serviceId: 'service-odk',
    nextChoices: ODKChoices
  });
  var DataSourceChoices = choiceListFactory({
    id: 'choices-data-source',
    children: [DataSourceServiceChoice, ODKChoices]
  });
  var Draft = draftFactory({ firstElement: DataSourceChoices });
  var CongenialEnigma = listenerFactory([
    DataSourceChoices,
    AlertChoices,
    DataDestinationChoices,
    Draft
  ]);
  CongenialEnigma.listen();
  return;

  /* ------------------------------------------------------------------------ */
       /* UI helper functions */
  /* ------------------------------------------------------------------------ */

  // Activates a step title (for example, "1 Choose a Data Source") and
  // deactivates the others. $descendant is an element nested under the step.
  function activateStepTitle($descendant) {
    var $h4 = $descendant.closest('tr').find('h4');
    if ($h4.hasClass('text-muted')) {
      $(TOP_SELECTOR + '> tbody > tr h4').addClass('text-muted');
      $h4.removeClass('text-muted');
    }
  }

  /* ------------------------------------------------------------------------ */
       /* Choice factories */
  /* ------------------------------------------------------------------------ */

  // nextChoices must be a choice list.
  function serviceChoiceFactory(options) {
    return {
      nextElement: function() {
        return options.nextChoices;
      },
      hasDraft: function() {
        return options.nextChoices.children[0].hasDraft();
      },
      restoreDraft: function() {
        var draft = this.hasDraft();
        if (draft)
          options.nextChoices.show();
        return draft;
      },
      listen: function() {
        // Adding a trailing space for ease of combining with other selectors.
        var choiceSelector = TOP_SELECTOR + '#' + options.choiceId + ' ';
        var serviceSelector = TOP_SELECTOR + '#' + options.serviceId + ' ';
        var otherServicesSelector = choiceSelector +
          '.service:not(#' + options.serviceId + ') ';
        $(document)
          .on('click', serviceSelector, function(e) {
            e.preventDefault();
            $(otherServicesSelector).fadeTo('slow', 0, function() {
              $(this).remove();
              options.nextChoices.show();
            });
            $(serviceSelector).fadeTo('slow', 1);
          })
          .on('click', otherServicesSelector, function(e) {
            e.preventDefault();
            options.nextChoices.hide();
            activateStepTitle($(choiceSelector));
            var id = '#' + $(this).attr('id');
            $(choiceSelector + '.service:not(' + id + ')').fadeTo('fast', 0.5);
            $(id).fadeTo('fast', 1);
          });
      }
    };
  }

  function choicePanelFactory(options) {
    var $panel, state;
    var choice = {
      nextElement: nextElement,
      state: state,
      hasDraft: hasDraft,
      restoreDraft: restoreDraft,
      complete: complete,
      revisit: revisit,
      listen: listen
    };
    if (options.type === 'radio')
      choice.val = valRadio;
    if (options.hidden) {
      choice.show = show;
      choice.hide = hide;
    }
    return choice;

    function nextElement() {
      return options.nextElement;
    }

    function state() {
      return state;
    }

    function hasDraft() {
      return $panel.data('hasDraft');
    }

    function restoreDraft() {
      var draft = hasDraft();
      if (draft)
        complete(true);
      return draft;
    }

    // Activates select radio buttons:
    //
    //   1. Shows radio buttons except those with a `filtered` class
    //   2. Selects/checks the first visible, enabled radio button
    //
    function activateRadio() {
      $panel.find('.radio.filtered').hide();
      var $visibleRadio = $panel.find('.radio:not(.filtered)');
      var $enabledRadio = $visibleRadio
                             .find('input:not(.permanently-disabled)')
                             .closest('.radio');
      if ($enabledRadio.length > 0) {
        $enabledRadio.removeClass('disabled');
        if ($enabledRadio.find(':checked').length === 0)
          $enabledRadio.find('input').first().prop('checked', true);
        $panel.find('.action-complete').show();
      }
      $visibleRadio.show();
    }

    /*
    Activates the panel by making the following changes:

      1. Invokes the beforeActivate callback
      2. Activates the step title under which the panel is nested
      3. Unmutes text, including the title
      4. Enables inputs except those with a permanently-disabled class
      5. Activates select radio buttons
      6. Hides the revisit action, shows the add action, and shows the complete
         action if appropriate
    */
    function activate() {
      if (state === 'active')
        return;
      if (options.beforeActivate)
        options.beforeActivate($panel);

      activateStepTitle($panel);
      $panel.removeClass('text-muted');
      $panel.find('.panel-title span').removeClass('text-muted');
      var $input = $panel.find('input, select, textarea');
      $input.filter(':not(.permanently-disabled)').prop('disabled', false);
      if ($panel.find('form:not(.form-paragraph)').length > 0)
        activateRadio();
      else
        $panel.find('.action-complete').show();
      $panel.find('.action-add').show();
      $panel.find('.action-revisit').hide();
    }

    function show() {
      if (state !== 'hidden')
        return;
      activate();
      $panel.show();
      state = 'active';
    }

    function hide() {
      if (state === 'hidden')
        return;
      nextElement().hide();
      $panel.hide();
      state = 'hidden';
    }

    function saveDraft() {
      var data = {};
      var selector = 'input[type="radio"]:checked, input[type!="radio"], ' +
        'select, textarea';
      $panel.find(selector).each(function() {
        var $el = $(this);
        var name = $el.data('draftName') || $el.attr('name');
        data[name] = $el.val();
      });
      $.post('/integrations/save_draft', data);
    }

    /*
    Updates the panel's UI after it has been completed:

      1. Mutes text, including the title
      2. Disables inputs
      3. Hides unchecked radio buttons
      4. Hides the add and complete actions and shows the revisit action
    */
    function updateUIAfterComplete() {
      $panel.addClass('text-muted');
      $panel.find('.panel-title span').addClass('text-muted');
      $panel.find('input[type="radio"]:not(:checked)').closest('.radio').hide();
      $panel.find('input, select, textarea').prop('disabled', true);
      $panel.find(':checked').closest('.radio').addClass('disabled');
      $panel.find('.action-add, .action-complete').hide();
      $panel.find('.action-revisit').show();
    }

    function complete(skipDraftSave) {
      if (state !== 'active')
        return;
      if (!skipDraftSave)
        saveDraft();
      updateUIAfterComplete();
      state = 'complete';
      nextElement().show();
    }

    function valRadio() {
      return $panel.find(':checked').val();
    }

    function revisit() {
      if (state !== 'complete')
        return;
      nextElement().hide();
      activate();
      state = 'active';
    }

    function listen() {
      // Adding a trailing space for ease of combining with other selectors.
      var selector = TOP_SELECTOR + '#' + options.id + ' ';
      $(document)
        .on('turbolinks:load', function() {
          $panel = $(selector);
          state = options.hidden ? 'hidden' : 'active';
        })
        .on('submit', selector, function(e) {
          e.preventDefault();
          complete();
        })
        .on('click', selector + '.action-revisit', function(e) {
          e.preventDefault();
          revisit();
        });
    }
  }

  function filteredChoicePanelFactory(options) {
    var filteredOptions = {
      type: 'radio',
      hidden: true,
      beforeActivate: beforeActivate
    };
    var combinedOptions = $.extend({}, options, filteredOptions);
    return choicePanelFactory(combinedOptions);

    function updateAddPath($choice) {
      var $addAction = $choice.find('.action-add');
      var path = $addAction
                   .data('path')
                   .replace('0', options.configuredServiceId());
      $addAction.find('a').prop('href', path);
    }

    function filterRadio($choice) {
      var $radio = $choice.find('.radio');
      $radio.addClass('filtered');
      var selector = '[data-configured-service-id="' +
        options.configuredServiceId() + '"]';
      $radio.filter(selector).removeClass('filtered');
    }

    function beforeActivate($choice) {
      updateAddPath($choice);
      filterRadio($choice);
    }
  }

  /* ------------------------------------------------------------------------ */
       /* Other component factories */
  /* ------------------------------------------------------------------------ */

  /*
  choiceListFactory() returns a "choice list," which groups together one or more
  other choices and/or choice lists. Think of a choice list as a tree, where
  choice lists are branches and choices are leaves. Options:

    id        HTML id of the choice list element
    children  Array of the choices and choice lists contained in the list
              (immediate children only)
  */
  function choiceListFactory(options) {
    var lastChild = options.children[options.children.length - 1];
    var $choices, hidden;
    return {
      children: options.children,
      nextElement: function() {
        return lastChild.nextElement();
      },
      state: function() {
        if (hidden)
          return 'hidden';
        return lastChild.state() === 'complete' ? 'complete' : 'active';
      },
      hasDraft: function() {
        return options.children[0].hasDraft();
      },
      restoreDraft: function() {
        for (var i = 0; i < options.children.length; i++) {
          if (!options.children[i].restoreDraft())
            return false;
        }
        return true;
      },
      show: function() {
        $choices.show();
        hidden = false;
        if (this.state() === 'complete')
          this.nextElement().show();
        else
          activateStepTitle($choices);
        hidden = false;
      },
      hide: function() {
        this.nextElement().hide();
        $choices.hide();
        hidden = true;
      },
      listen: function() {
        $(document).on('turbolinks:load', function() {
          $choices = $('#' + options.id);
          hidden = true;
        });
        options.children.forEach(function(child) {
          child.listen();
        });
      }
    };
  }

  function finalizationFactory(id) {
    var selector = '#' + id;
    return {
      show: show,
      hide: hide
    };

    function updatePushUrl() {
      var $pushUrl = $('#push-url');
      var url = $pushUrl
                  .data('url')
                  .replace('/0', '/' + ServerChoice.val())
                  .replace('/1', '/' + FormChoice.val());
      $pushUrl.text(url);
    }

    function copyFields() {
      $('#data_source_id').val(FormChoice.val());
      $('#rule_data_field_name').val($('#field_name').val());
      $('#rule_type').val($('#rule_class').val());
      $('#rule_data_value').val($('#rule_value').val());
      $('#message').val($('#alert_message').val());
      $('#data_destination_id').val(RecipientListChoice.val());
    }

    function show() {
      updatePushUrl();
      copyFields();
      activateStepTitle($('#choices-data-destination'));
      $(selector).show();
    }

    function hide() {
      $(selector).hide();
    }
  }

  function draftFactory(options) {
    return {
      listen: function() {
        $(document).on('turbolinks:load', function() {
          for (var element = options.firstElement;
               element && element.restoreDraft && element.restoreDraft();
               element = element.nextElement())
            ;
        });
      }
    };
  }

  // Returns an object that listens by calling other listeners.
  function listenerFactory(listeners) {
    return {
      listen: function() {
        listeners.forEach(function(listener) {
          listener.listen();
        });
      }
    };
  }
})();
<|endoftext|>


<commit_before>var Project = Backbone.Model.extend({
    SRS_DEFAULT: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
    + '+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
    STYLESHEET_DEFAULT: [{
        id: 'style.mss',
        data: '#world {\n  polygon-fill: #eee;\n  line-color: #ccc;\n  '
            + 'line-width: 0.5;\n}'
    }],
    LAYER_DEFAULT: [{
        id: 'world',
        name: 'world',
        srs: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
        + '+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
        Datasource: {
            file: 'http://tilemill-data.s3.amazonaws.com/world_borders_merc.zip',
            type: 'shape',
            estimate_extent: 'true',
            id: 'world'
        }
    }],
    initialize: function(attributes) {
        if (!this.get('srs')) {
            this.set({'srs': this.SRS_DEFAULT});
        }
        if (!this.get('Stylesheet')) {
            this.set({
                'Stylesheet': new StylesheetList(this.STYLESHEET_DEFAULT, {
                    parent: this
                })
            });
        }
        if (!this.get('Layer')) {
            this.set({
                'Layer': new LayerList(this.LAYER_DEFAULT, {
                    parent: this
                })
            });
        }
    },
    parse: function(response) {
        var self = this;
        // Instantiate StylesheetList and LayerList collections from JSON lists
        // of plain JSON objects.
        response.Stylesheet = new StylesheetList(response.Stylesheet ?
                response.Stylesheet :
                [], { parent: this });
        response.Layer = new LayerList(response.Layer ?
                response.Layer : [], { parent: this });
        return response;
    },
    // Override url() method for convenience so we don't always need a
    // collection reference around for CRUD operations on a single model.
    url: function() {
        return '/api/project/' + this.id;
    },
    /**
     * Base64 encode this project's MML URL.
     */
    project64: function(options) {
        // `window.location.origin` is not available in all browsers like
        // Firefox. @TODO This approach won't allow TileMill to be installed in
        // a subdirectory. Fix.
        var url = [
            window.location.protocol,
            window.location.host].join('//') + this.url();
        if (options.signed) {
            var md5 = new MD5();
            url += '?' + md5.digest(JSON.stringify(this)).substr(0, 6);
        }
        return Base64.urlsafe_encode(url);
    },
    /**
     * Layer URL based on the model URL.
     */
    layerURL: function(options) {
        return [window.location.protocol, window.location.host].join('//')
            + '/tile/'
            + this.project64(options)
            + '/${z}/${x}/${y}.png';
    },
    validate: function(attributes) {
        if (/^[a-z0-9\-_]+$/i.test(attributes.id) === false) {
            return 'Name must contain only letters, numbers, dashes, '
                + 'and underscores.';
        }
    }
});

var ProjectList = Backbone.Collection.extend({
    model: Project,
    url: '/api/project',
    comparator: function(project) {
        return project.get('id');
    }
});

var ProjectListView = Backbone.View.extend({
    id: 'ProjectListView',
    initialize: function() {
        _.bindAll(this, 'render', 'add', 'about');
        this.collection.bind('add', this.render);
        this.collection.bind('remove', this.render);
        this.collection.fetch({
            success: this.render,
            error: this.render
        });
    },
    render: function() {
        // Render the projects wrapper if not present.
        if ($(this.el).has('ul.projects').length === 0) {
            $(this.el).html(ich.ProjectListView());
            window.app.el.html(this.el);
        }

        // Add a row view for each project. Note that we use a pointer as the
        // projects are added to ensure that when new projects are added on a
        // re-render they are placed at the correct index in the list.
        var that = this;
        var pointer = null;
        this.collection.each(function(project) {
            if (!project.view) {
                project.view = new ProjectRowView({
                    model: project,
                    collection: this.collection
                });
                if (!pointer) {
                    $('ul.projects', self.el).prepend(project.view.el);
                }
                else {
                    $(pointer).after(project.view.el);
                }
            }
            pointer = project.view.el;
        });
        return this;
    },
    events: {
        'click input.submit': 'add',
        'click div#header a.info': 'about'
    },
    add: function() {
        window.app.loading();
        var that = this;
        var project = new Project;
        var success = project.set(
            { id: $('input.text', this.el).val() },
            { error: this.showError }
        );
        if (success) {
            project.save(project, {
                success: function() {
                    $('input.text', this.el).val('');
                    that.collection.add(project);
                    window.app.done();
                },
                error: this.showError
            });
        }
        return false;
    },
    about: function() {
        window.app.message('About TileLive', '@TODO: Put something facinating here.');
        return false;
    },
    showError: function(model, error) {
        window.app.done();
        window.app.message('Error', error);
    }
});

var ProjectRowView = Backbone.View.extend({
    tagName: 'li',
    className: 'clearfix',
    initialize: function() {
        _.bindAll(this, 'render', 'del');
        this.render();
    },
    // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#lon.2Flat_to_tile_numbers_2
    thumb: function() {
        var center = this.model.get('center') || {lat: 0, lon: 0, zoom: 2};
        var z = center.zoom;
        var lat_rad = center.lat * Math.PI / 180;
        var x = parseInt((center.lon + 180.0) / 360.0 * Math.pow(2,z));
        var y = parseInt((1.0 - Math.log(Math.tan(lat_rad) + (1 / Math.cos(lat_rad))) / Math.PI) / 2.0 * Math.pow(2,z));
        var url = this.model.layerURL({ signed: true });
        return url.replace('${z}', z).replace('${x}', x).replace('${y}', y);
    },
    render: function() {
        $(this.el).html(ich.ProjectRowView({
            id: this.model.get('id'),
            thumb: this.thumb()
        }));
        return this;
    },
    events: {
        'click .delete': 'del'
    },
    del: function() {
        var that = this;
        window.app.loading();
        if (confirm('Are you sure you want to delete this project?')) {
            this.model.destroy({
                success: function() {
                    that.remove();
                    window.app.done();
                },
                error: function() {
                    window.app.done();
                    window.app.message('Error', 'The project could not be deleted.');
                }
            });
        }
        else {
            window.app.done();
        }
        return false;
    }
});

var ProjectView = Backbone.View.extend({
    id: 'ProjectView',
    events: {
        'click #header a.save': 'saveProject',
        'click #header a.info': 'projectInfo',
        'click #header a.minimal': 'minimal',
        'click #header a.home': 'home'
    },
    initialize: function() {
        _.bindAll(this, 'render', 'saveProject', 'projectInfo',
            'home', 'minimal', 'changed');
        this.model.view = this;
        this.model.bind('change', this.changed);
        this.model.fetch({
            success: this.render,
            error: function(err, data) {
                var obj = $.parseJSON(data.responseText);
                window.app.message('Error', obj.message);
            }
        });
    },
    render: function() {
        $(this.el).html(ich.ProjectView(this.model));

        var layers = new LayerListView({
            collection: this.model.get('Layer'),
            project: this.model
        }),
            stylesheets = new StylesheetListView({
            collection: this.model.get('Stylesheet'),
            project: this.model
        }),
            colors = new ColorSwatchesToolView({
            collection: new ColorSwatchesList(null, {
                project: this.model
            }),
            project: this.model
        }),
            map = new MapView({
            model: this.model
        }),
            colorPicker = new ColorPickerToolView({
            model: this.model
        }),
            fontPicker = new FontPickerToolView({
            model: new Abilities,
            project: this.model
        });

        $('#sidebar', this.el).append(layers.el);
        $('#sidebar', this.el).append(colors.el);
        $('#sidebar', this.el).append(map.el);
        $('#toolbar', this.el).append(colorPicker.el);
        $('#toolbar', this.el).append(fontPicker.el);
        $('#main', this.el).append(stylesheets.el);

        window.app.el.html(this.el);
        window.app.trigger('ready');
        // Save project immediately to trigger validation.
        this.saveProject();
        return this;
    },
    saveProject: function() {
        var that = this;
        this.model.save(this.model, {
            success: function() {
                that.model.trigger('save');
                $('#header a.save', self.el).removeClass('changed');
                $('.CodeMirror-line-numbers div')
                    .removeClass('syntax-error')
                    .attr('title', '')
                    .unbind('mouseenter mouseleave'); // Removes tipsy.
            },
            error: function(err, data) {
                if (data.status == 500) {
                    var err_obj = $.parseJSON(data.responseText);
                    if (err_obj.line) {
                        var editor = _.detect(
                            that.model.view.stylesheets.collection.models,
                            function(s) {
                                return s.id == err_obj.filename;
                        });
                        $('div.CodeMirror-line-numbers div:nth-child('
                            + err_obj.line
                            + ')',
                            editor.view.codemirror.lineNumbers)
                            .addClass('syntax-error')
                            .attr('title', err_obj.message)
                            .tipsy({gravity: 'w'});
                    } else {
                        window.app.message('Error', err_obj.message);
                    }
                }
            }
        });
        return false;
    },
    projectInfo: function() {
        window.app.message('Project Info', {
            'tilelive_url': this.model.layerURL({signed: true}),
            'mml_url': [
                window.location.protocol,
                window.location.host
            ].join('//') + this.model.url()
        }, 'projectInfo');
        return false;
    },
    home: function() {
        return (!$('#header a.save', this.el).is('.changed') || confirm('You have unsaved changes. Are you sure you want to close this project?'));
    },
    minimal: function() {
        $('a.minimal', this.el).toggleClass('active');
        if ($('a.minimal', this.el).is('.active')) {
            $(this.el).addClass('minimal');
            this.watcher = new Watcher(this.model);
        }
        else {
            $(this.el).removeClass('minimal');
            this.watcher.destroy();
        }
        return false;
    },
    changed: function() {
        $('#header a.save', this.el).addClass('changed');
    }
});

/**
 * Watcher.
 * Class for updating project in minimal mode.
 */
var Watcher = function(model) {
    _.bindAll(this, 'fetch', 'destroy');
    var model = model;
    this.model = model;
    this.model.bind('change', this.fetch);
    this.md5 = new MD5();
    this.current = this.md5.digest(JSON.stringify(this.model));
    this.watcher = setInterval(function() { model.fetch(); }, 1000);
};

Watcher.prototype.fetch = function() {
    var state = this.md5.digest(JSON.stringify(this.model));
    if (this.current !== state) {
        this.current = state;
        this.model.trigger('save');
    }
};

Watcher.prototype.destroy = function() {
    window.clearInterval(this.watcher);
};

<commit_msg>Add Map selector to default project layout. Fixes #88.<commit_after>var Project = Backbone.Model.extend({
    SRS_DEFAULT: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
    + '+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
    STYLESHEET_DEFAULT: [{
        id: 'style.mss',
        data: 'Map {\n'
            + '  background-color: #fff;\n'
            + '}\n\n'
            + '#world {\n'
            + '  polygon-fill: #eee;\n'
            + '  line-color: #ccc;\n'
            + '  line-width: 0.5;\n'
            + '}'
    }],
    LAYER_DEFAULT: [{
        id: 'world',
        name: 'world',
        srs: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
        + '+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
        Datasource: {
            file: 'http://tilemill-data.s3.amazonaws.com/world_borders_merc.zip',
            type: 'shape',
            estimate_extent: 'true',
            id: 'world'
        }
    }],
    initialize: function(attributes) {
        if (!this.get('srs')) {
            this.set({'srs': this.SRS_DEFAULT});
        }
        if (!this.get('Stylesheet')) {
            this.set({
                'Stylesheet': new StylesheetList(this.STYLESHEET_DEFAULT, {
                    parent: this
                })
            });
        }
        if (!this.get('Layer')) {
            this.set({
                'Layer': new LayerList(this.LAYER_DEFAULT, {
                    parent: this
                })
            });
        }
    },
    parse: function(response) {
        var self = this;
        // Instantiate StylesheetList and LayerList collections from JSON lists
        // of plain JSON objects.
        response.Stylesheet = new StylesheetList(response.Stylesheet ?
                response.Stylesheet :
                [], { parent: this });
        response.Layer = new LayerList(response.Layer ?
                response.Layer : [], { parent: this });
        return response;
    },
    // Override url() method for convenience so we don't always need a
    // collection reference around for CRUD operations on a single model.
    url: function() {
        return '/api/project/' + this.id;
    },
    /**
     * Base64 encode this project's MML URL.
     */
    project64: function(options) {
        // `window.location.origin` is not available in all browsers like
        // Firefox. @TODO This approach won't allow TileMill to be installed in
        // a subdirectory. Fix.
        var url = [
            window.location.protocol,
            window.location.host].join('//') + this.url();
        if (options.signed) {
            var md5 = new MD5();
            url += '?' + md5.digest(JSON.stringify(this)).substr(0, 6);
        }
        return Base64.urlsafe_encode(url);
    },
    /**
     * Layer URL based on the model URL.
     */
    layerURL: function(options) {
        return [window.location.protocol, window.location.host].join('//')
            + '/tile/'
            + this.project64(options)
            + '/${z}/${x}/${y}.png';
    },
    validate: function(attributes) {
        if (/^[a-z0-9\-_]+$/i.test(attributes.id) === false) {
            return 'Name must contain only letters, numbers, dashes, '
                + 'and underscores.';
        }
    }
});

var ProjectList = Backbone.Collection.extend({
    model: Project,
    url: '/api/project',
    comparator: function(project) {
        return project.get('id');
    }
});

var ProjectListView = Backbone.View.extend({
    id: 'ProjectListView',
    initialize: function() {
        _.bindAll(this, 'render', 'add', 'about');
        this.collection.bind('add', this.render);
        this.collection.bind('remove', this.render);
        this.collection.fetch({
            success: this.render,
            error: this.render
        });
    },
    render: function() {
        // Render the projects wrapper if not present.
        if ($(this.el).has('ul.projects').length === 0) {
            $(this.el).html(ich.ProjectListView());
            window.app.el.html(this.el);
        }

        // Add a row view for each project. Note that we use a pointer as the
        // projects are added to ensure that when new projects are added on a
        // re-render they are placed at the correct index in the list.
        var that = this;
        var pointer = null;
        this.collection.each(function(project) {
            if (!project.view) {
                project.view = new ProjectRowView({
                    model: project,
                    collection: this.collection
                });
                if (!pointer) {
                    $('ul.projects', self.el).prepend(project.view.el);
                }
                else {
                    $(pointer).after(project.view.el);
                }
            }
            pointer = project.view.el;
        });
        return this;
    },
    events: {
        'click input.submit': 'add',
        'click div#header a.info': 'about'
    },
    add: function() {
        window.app.loading();
        var that = this;
        var project = new Project;
        var success = project.set(
            { id: $('input.text', this.el).val() },
            { error: this.showError }
        );
        if (success) {
            project.save(project, {
                success: function() {
                    $('input.text', this.el).val('');
                    that.collection.add(project);
                    window.app.done();
                },
                error: this.showError
            });
        }
        return false;
    },
    about: function() {
        window.app.message('About TileLive', '@TODO: Put something facinating here.');
        return false;
    },
    showError: function(model, error) {
        window.app.done();
        window.app.message('Error', error);
    }
});

var ProjectRowView = Backbone.View.extend({
    tagName: 'li',
    className: 'clearfix',
    initialize: function() {
        _.bindAll(this, 'render', 'del');
        this.render();
    },
    // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#lon.2Flat_to_tile_numbers_2
    thumb: function() {
        var center = this.model.get('center') || {lat: 0, lon: 0, zoom: 2};
        var z = center.zoom;
        var lat_rad = center.lat * Math.PI / 180;
        var x = parseInt((center.lon + 180.0) / 360.0 * Math.pow(2,z));
        var y = parseInt((1.0 - Math.log(Math.tan(lat_rad) + (1 / Math.cos(lat_rad))) / Math.PI) / 2.0 * Math.pow(2,z));
        var url = this.model.layerURL({ signed: true });
        return url.replace('${z}', z).replace('${x}', x).replace('${y}', y);
    },
    render: function() {
        $(this.el).html(ich.ProjectRowView({
            id: this.model.get('id'),
            thumb: this.thumb()
        }));
        return this;
    },
    events: {
        'click .delete': 'del'
    },
    del: function() {
        var that = this;
        window.app.loading();
        if (confirm('Are you sure you want to delete this project?')) {
            this.model.destroy({
                success: function() {
                    that.remove();
                    window.app.done();
                },
                error: function() {
                    window.app.done();
                    window.app.message('Error', 'The project could not be deleted.');
                }
            });
        }
        else {
            window.app.done();
        }
        return false;
    }
});

var ProjectView = Backbone.View.extend({
    id: 'ProjectView',
    events: {
        'click #header a.save': 'saveProject',
        'click #header a.info': 'projectInfo',
        'click #header a.minimal': 'minimal',
        'click #header a.home': 'home'
    },
    initialize: function() {
        _.bindAll(this, 'render', 'saveProject', 'projectInfo',
            'home', 'minimal', 'changed');
        this.model.view = this;
        this.model.bind('change', this.changed);
        this.model.fetch({
            success: this.render,
            error: function(err, data) {
                var obj = $.parseJSON(data.responseText);
                window.app.message('Error', obj.message);
            }
        });
    },
    render: function() {
        $(this.el).html(ich.ProjectView(this.model));

        var layers = new LayerListView({
            collection: this.model.get('Layer'),
            project: this.model
        }),
            stylesheets = new StylesheetListView({
            collection: this.model.get('Stylesheet'),
            project: this.model
        }),
            colors = new ColorSwatchesToolView({
            collection: new ColorSwatchesList(null, {
                project: this.model
            }),
            project: this.model
        }),
            map = new MapView({
            model: this.model
        }),
            colorPicker = new ColorPickerToolView({
            model: this.model
        }),
            fontPicker = new FontPickerToolView({
            model: new Abilities,
            project: this.model
        });

        $('#sidebar', this.el).append(layers.el);
        $('#sidebar', this.el).append(colors.el);
        $('#sidebar', this.el).append(map.el);
        $('#toolbar', this.el).append(colorPicker.el);
        $('#toolbar', this.el).append(fontPicker.el);
        $('#main', this.el).append(stylesheets.el);

        window.app.el.html(this.el);
        window.app.trigger('ready');
        // Save project immediately to trigger validation.
        this.saveProject();
        return this;
    },
    saveProject: function() {
        var that = this;
        this.model.save(this.model, {
            success: function() {
                that.model.trigger('save');
                $('#header a.save', self.el).removeClass('changed');
                $('.CodeMirror-line-numbers div')
                    .removeClass('syntax-error')
                    .attr('title', '')
                    .unbind('mouseenter mouseleave'); // Removes tipsy.
            },
            error: function(err, data) {
                if (data.status == 500) {
                    var err_obj = $.parseJSON(data.responseText);
                    if (err_obj.line) {
                        var editor = _.detect(
                            that.model.view.stylesheets.collection.models,
                            function(s) {
                                return s.id == err_obj.filename;
                        });
                        $('div.CodeMirror-line-numbers div:nth-child('
                            + err_obj.line
                            + ')',
                            editor.view.codemirror.lineNumbers)
                            .addClass('syntax-error')
                            .attr('title', err_obj.message)
                            .tipsy({gravity: 'w'});
                    } else {
                        window.app.message('Error', err_obj.message);
                    }
                }
            }
        });
        return false;
    },
    projectInfo: function() {
        window.app.message('Project Info', {
            'tilelive_url': this.model.layerURL({signed: true}),
            'mml_url': [
                window.location.protocol,
                window.location.host
            ].join('//') + this.model.url()
        }, 'projectInfo');
        return false;
    },
    home: function() {
        return (!$('#header a.save', this.el).is('.changed') || confirm('You have unsaved changes. Are you sure you want to close this project?'));
    },
    minimal: function() {
        $('a.minimal', this.el).toggleClass('active');
        if ($('a.minimal', this.el).is('.active')) {
            $(this.el).addClass('minimal');
            this.watcher = new Watcher(this.model);
        }
        else {
            $(this.el).removeClass('minimal');
            this.watcher.destroy();
        }
        return false;
    },
    changed: function() {
        $('#header a.save', this.el).addClass('changed');
    }
});

/**
 * Watcher.
 * Class for updating project in minimal mode.
 */
var Watcher = function(model) {
    _.bindAll(this, 'fetch', 'destroy');
    var model = model;
    this.model = model;
    this.model.bind('change', this.fetch);
    this.md5 = new MD5();
    this.current = this.md5.digest(JSON.stringify(this.model));
    this.watcher = setInterval(function() { model.fetch(); }, 1000);
};

Watcher.prototype.fetch = function() {
    var state = this.md5.digest(JSON.stringify(this.model));
    if (this.current !== state) {
        this.current = state;
        this.model.trigger('save');
    }
};

Watcher.prototype.destroy = function() {
    window.clearInterval(this.watcher);
};

<|endoftext|>


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

CLASS({
  package: 'foam.graphics',
  name: 'DOMSizedCViewView',

  extendsModel: 'foam.graphics.AbstractCViewView',

  help: 'DOM wrapper for a CView that takes its DOM sizing and applies it to the child CView.',

  documentation: function() {/*
    DOM wrapper for a $$DOC{ref:'foam.graphics.CView'} that takes its DOM sizing and
    applies it to the child CView.
  */},

  properties: [
    {
      name: 'cview',
      postSet: function(_, cview) {
        cview.view = this;
        cview.x = 0;
        cview.y = 0;
        this.resize();
      }
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      this.X.dynamic(function() { this.width; this.height; }.bind(this),
                     this.resize);
    },
    toHTML: function() {
      var className = this.className ? ' class="' + this.className + '"' : '';
      return '<canvas id="' + this.id + '"' + className + ' style="width: 100%; height: 100%"></canvas>';
    },
    initHTML: function() {
      this.SUPER();
      this.$.addEventListener('resize', this.resize);
      this.resize();
    }
  },
  listeners: [
    {
      name: 'resize',
      isFramed: true,
      code: function() {
        if ( ! this.$ ) return;
        this.width = this.$.clientWidth / this.scalingRatio;
        this.height = this.$.clientHeight / this.scalingRatio;
        this.$.width = this.width;   // tell the DOM to update its style settings
        this.$.height = this.height; // otherwise scaling happens
        if ( ! this.cview ) return;
        this.cview.width   = this.width;
        this.cview.height  = this.height;
        this.paint();
      }
    }
  ]
});
<commit_msg>Fixed DOMSizedCViewView for high dpi displays<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.graphics',
  name: 'DOMSizedCViewView',

  extendsModel: 'foam.graphics.AbstractCViewView',

  help: 'DOM wrapper for a CView that takes its DOM sizing and applies it to the child CView.',

  documentation: function() {/*
    DOM wrapper for a $$DOC{ref:'foam.graphics.CView'} that takes its DOM sizing and
    applies it to the child CView.
  */},

  properties: [
    {
      name: 'cview',
      postSet: function(_, cview) {
        cview.view = this;
        cview.x = 0;
        cview.y = 0;
        this.resize();
      }
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      this.X.dynamic(function() { this.width; this.height; }.bind(this),
                     this.resize);
    },
    toHTML: function() {
      var className = this.className ? ' class="' + this.className + '"' : '';
      return '<canvas id="' + this.id + '"' + className + ' style="width: 100%; height: 100%"></canvas>';
    },
    initHTML: function() {
      this.SUPER();
      this.$.addEventListener('resize', this.resize);
      this.resize();
    }
  },
  listeners: [
    {
      name: 'resize',
      isFramed: true,
      code: function() {
        if ( ! this.$ ) return;
        this.width = this.$.clientWidth / this.scalingRatio;
        this.height = this.$.clientHeight / this.scalingRatio;
        this.$.width = this.canvasWidth();   // tell the DOM to update its style settings
        this.$.height = this.canvasHeight(); // otherwise scaling happens
        if ( ! this.cview ) return;
        this.cview.width   = this.width;
        this.cview.height  = this.height;
        this.paint();
      }
    }
  ]
});
<|endoftext|>