<commit_before>var config = require('../src/config/config')
var client = require('mongodb').MongoClient

var collections = [
    {
        name: 'categories',
        exists: false,
        data: [{
            name: 'annat',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'chips',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'e-nummer',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'glass',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'godis',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'hudvård',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'kosttillskott',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'makeup',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'margarin',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'ost',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'schampo',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'tandkräm',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'vegokött',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'vin',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'växtdryck',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'växtgrädde',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'öl',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'sprit',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'cider',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'indiskt',
            type: '3',
            subcategory: 'asiatiskt'
        },{
            name: 'kinesiskt',
            type: '3',
            subcategory: 'asiatiskt'
        },{
            name: 'thailändskt',
            type: '3',
            subcategory: 'asiatiskt'
        },{
            name: 'japanskt',
            type: '3',
            subcategory: 'asiatiskt'
        },{
            name: 'grekiskt',
            type: '3',
            subcategory: 'europeiskt'
        },{
            name: 'libanesiskt',
            type: '3',
            subcategory: 'mellanöstern'
        },{
            name: 'italienskt',
            type: '3',
            subcategory: 'europeiskt'
        },{
            name: 'vietnamesiskt',
            type: '3',
            subcategory: 'asiatiskt'
        },{
            name: 'persiskt',
            type: '3',
            subcategory: 'mellanöstern'
        },{
            name: 'mexikanskt',
            type: '3',
            subcategory: 'amerikanskt'
        },{
            name: 'franskt',
            type: '3',
            subcategory: 'europeiskt'
        },{
            name: 'svenskt',
            type: '3',
            subcategory: 'europeiskt'
        },{
            name: 'brittiskt',
            type: '3',
            subcategory: 'europeiskt'
        },{
            name: 'tyskt',
            type: '3',
            subcategory: 'asiatiskt'
        }]
    },
    {
        name: 'cities',
        exists: false,
        data: [{
                name: 'stockholm'
            },{
                name: 'göteborg'
            },{
                name: 'malmö'
            },{
                name: 'uppsala'
            },{
                name: 'västerås'
            },{
                name: 'örebro'
            },{
                name: 'linköping'
            },{
                name: 'helsingborg'
            },{
                name: 'jönköping'
            },{
                name: 'norrköping'
            },{
                name: 'lund'
            }]
    },
    {
        name: 'images',
        exists: false
    },
    {
        name: 'pages',
        exists: false
    },
    {
        name: 'revisions',
        exists: false
    },
    {
        name: 'sessions',
        exists: false
    },
    {
        name: 'users',
        exists: false
    },
    {
        name: 'votes',
        exists: false
    }
]

function containsObject(obj, array) {
    var i;
    for (i = 0; i < array.length; i++) {
        if (array[i] === obj) {
            return true;
        }
    }

    return false;
}

function createCollection(name) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.createCollection(name, function(err, dbCollection) {
            if(err) throw err
            console.log('Created collection: ' + name)
            db.close()
        })
    })
}

function insertDataToCollection(name, data) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.collection(name).insert(data, function(err, docs) {
            if (err) throw err
            console.log('Successfully inserted ' + docs.insertedCount + ' documents to collection ' + name)

            db.close()
        })
    })
}

function verifyDataInCollection(name, data) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        var dataFound = []

        db.collection(name).find({}).toArray(function(err, docs){
            if (err) throw err
            for (var doc in docs) {
                if (docs.hasOwnProperty(doc)) {
                    for (var entry in data) {
                        if (data.hasOwnProperty(entry)) {
                            if(data[entry].name == docs[doc].name) {

                                //Check if it already exists in the dataFound array (in case of duplicate data in db)
                                if(!containsObject(data[entry],dataFound)) {
                                    //Entry wasn't found in array, add it
                                    dataFound.push(data[entry])
                                }
                            }
                        }
                    }
                }
            }

            if(dataFound.length < data.length) {
                var dataToInsert = []
                for (var i = 0; i < data.length; i++) {
                    if(!containsObject(data[i], dataFound)) {
                        dataToInsert.push(data[i])
                    }
                }
                //Insert missing data
                insertDataToCollection(name, dataToInsert)
            }

            db.close()
        })

    })
}

function verifyPageRevision(page) {
    client.connect(config.database.host+config.database.name, function(err, db){
    if (err) throw err
        db.collection('revisions').find({post_id:page._id}).toArray(function(err, doc) {
            if (err) throw err
            if(doc.length <= 0) {
                insertPageRevision(page)
            } else {
                for(i = 0; i < doc.length; i++) {
                    var current = doc[i]
                    var properties = {
                        post_id: function() {
                            return { post_id: null }
                        },
                        revision: function() {
                            return { revision: Math.floor(Date.now() / 1000) }
                        },
                        revisions: function() {
                            return { revision: {} }
                        },
                        modified: function() {
                            return { modified: new Date() }
                        },
                        pending: function() {
                            return { pending: 0 }
                        }
                    }

                    for(var property in properties) {
                        if( property in current ) {
                            console.log('Verified property ' + property + ' of revision: ' + current._id)
                        } else {
                            //Update revision document with given property
                            var update = properties[property]()
                            updatePageRevisionProperty(current._id, update )
                        }
                    }
                }
            }

            db.close()
        })
    })
}

function updatePageRevisionProperty(id, object) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.collection('revisions').update({_id:id},{ $set: object }, function(err, result) {
            if (err) throw err

            console.log('Updated property ' + Object.keys(object) + ' for revision: ' + id)

            db.close()
        })
    })
}

function insertPageRevision(page) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        var timestamp = ( new Date(page.timestamp.created) / 1000 ) //Unix timestamp
        var revision = {
            post_id: page._id,
            pending: 0,
            modified: "2016-02-03T22:22:12Z",
            revision: timestamp,
            revisions: {}
        }

        revision.revisions[timestamp] = page.post
        revision.revisions[timestamp].meta = {
            accepted: true, //always accept current page version
            user_info: page.user_info,
            timestamp: page.timestamp
        }

        db.collection('revisions').insert(revision, function(err, result) {
            if (err) throw err

            if(result.insertedCount > 0) {
                console.log('Skapade revision document för sida ' + page.title)
            }

            db.close()
        })
    })
}

/* Check for collections */
client.connect(config.database.host+config.database.name, function(err, db){
    if(err) throw err

    db.listCollections().toArray(function(err, dbCollections) {
        if(err) throw err

        for (var collection in collections) {
            if (collections.hasOwnProperty(collection)) {
                for (var dbCollection in dbCollections) {
                    if (dbCollections.hasOwnProperty(dbCollection)) {
                        if(dbCollections[dbCollection].hasOwnProperty('name')) {
                            if(dbCollections[dbCollection].name == collections[collection].name) {
                                collections[collection].exists = true
                            }
                        }
                    }
                }
            }
        }

        /* Create collections that don't exist */
        createCollections = []

        for (var collection in collections) {
            if (collections.hasOwnProperty(collection)) {
                if(collections[collection].exists == false) {
                    createCollection(collections[collection].name)

                    if(collections[collection].hasOwnProperty('data')) {
                        insertDataToCollection(collections[collection].name, collections[collection].data)
                    }
                } else {
                    //Check that all data in that collection is inserted
                    if(collections[collection].hasOwnProperty('data')) {
                        //Loop over data
                        verifyDataInCollection(collections[collection].name, collections[collection].data)
                    }
                }
            }
        }

        /* Create revision document for pages that don't have one */
        db.collection('pages').find({}).toArray(function(err, docs){
            if (err) throw err
            if(docs.length > 0) {
                for (var i = docs.length - 1; i >= 0; i--) {
                    verifyPageRevision(docs[i])
                }

                db.close()
            }
        })
    })

    process.on('exit', function() {
        console.log('*** Configure done! ***')
    });
})
<commit_msg>Rearrange categories a bit more simple<commit_after>var config = require('../src/config/config')
var client = require('mongodb').MongoClient

var collections = [
    {
        name: 'categories',
        exists: false,
        data: [{
            name: 'annat',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'chips',
            type: '4',
            subcategory: 'snacks'
        },{
            name: 'e-nummer',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'glass',
            type: '4',
            subcategory: 'snacks'
        },{
            name: 'godis',
            type: '4',
            subcategory: 'snacks'
        },{
            name: 'hudvård',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'kosttillskott',
            type: '4',
            subcategory: 'övrigt'
        },{
            name: 'makeup',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'margarin',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'ost',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'schampo',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'tandkräm',
            type: '4',
            subcategory: 'kropp'
        },{
            name: 'vegokött',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'vin',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'växtdryck',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'växtgrädde',
            type: '4',
            subcategory: 'livsmedel'
        },{
            name: 'öl',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'sprit',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'cider',
            type: '4',
            subcategory: 'alkohol'
        },{
            name: 'indiskt',
            type: '3'
        },{
            name: 'kinesiskt',
            type: '3'
        },{
            name: 'etiopiskt',
            type: '3'
        },{
            name: 'koreanskt',
            type: '3'
        },{
            name: 'thailändskt',
            type: '3'
        },{
            name: 'japanskt',
            type: '3'
        },{
            name: 'grekiskt',
            type: '3'
        },{
            name: 'libanesiskt',
            type: '3'
        },{
            name: 'italienskt',
            type: '3'
        },{
            name: 'vietnamesiskt',
            type: '3'
        },{
            name: 'persiskt',
            type: '3'
        },{
            name: 'mexikanskt',
            type: '3'
        },{
            name: 'franskt',
            type: '3'
        },{
            name: 'svenskt',
            type: '3'
        },{
            name: 'brittiskt',
            type: '3'
        },{
            name: 'tyskt',
            type: '3'
        }]
    },
    {
        name: 'cities',
        exists: false,
        data: [{
                name: 'stockholm'
            },{
                name: 'göteborg'
            },{
                name: 'malmö'
            },{
                name: 'uppsala'
            },{
                name: 'västerås'
            },{
                name: 'örebro'
            },{
                name: 'linköping'
            },{
                name: 'helsingborg'
            },{
                name: 'jönköping'
            },{
                name: 'norrköping'
            },{
                name: 'lund'
            }]
    },
    {
        name: 'images',
        exists: false
    },
    {
        name: 'pages',
        exists: false
    },
    {
        name: 'revisions',
        exists: false
    },
    {
        name: 'sessions',
        exists: false
    },
    {
        name: 'users',
        exists: false
    },
    {
        name: 'votes',
        exists: false
    }
]

function containsObject(obj, array) {
    var i;
    for (i = 0; i < array.length; i++) {
        if (array[i] === obj) {
            return true;
        }
    }

    return false;
}

function createCollection(name) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.createCollection(name, function(err, dbCollection) {
            if(err) throw err
            console.log('Created collection: ' + name)
            db.close()
        })
    })
}

function insertDataToCollection(name, data) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.collection(name).insert(data, function(err, docs) {
            if (err) throw err
            console.log('Successfully inserted ' + docs.insertedCount + ' documents to collection ' + name)

            db.close()
        })
    })
}

function verifyDataInCollection(name, data) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        var dataFound = []

        db.collection(name).find({}).toArray(function(err, docs){
            if (err) throw err
            for (var doc in docs) {
                if (docs.hasOwnProperty(doc)) {
                    for (var entry in data) {
                        if (data.hasOwnProperty(entry)) {
                            if(data[entry].name == docs[doc].name) {

                                //Check if it already exists in the dataFound array (in case of duplicate data in db)
                                if(!containsObject(data[entry],dataFound)) {
                                    //Entry wasn't found in array, add it
                                    dataFound.push(data[entry])
                                }
                            }
                        }
                    }
                }
            }

            if(dataFound.length < data.length) {
                var dataToInsert = []
                for (var i = 0; i < data.length; i++) {
                    if(!containsObject(data[i], dataFound)) {
                        dataToInsert.push(data[i])
                    }
                }
                //Insert missing data
                insertDataToCollection(name, dataToInsert)
            }

            db.close()
        })

    })
}

function verifyPageRevision(page) {
    client.connect(config.database.host+config.database.name, function(err, db){
    if (err) throw err
        db.collection('revisions').find({post_id:page._id}).toArray(function(err, doc) {
            if (err) throw err
            if(doc.length <= 0) {
                insertPageRevision(page)
            } else {
                for(i = 0; i < doc.length; i++) {
                    var current = doc[i]
                    var properties = {
                        post_id: function() {
                            return { post_id: null }
                        },
                        revision: function() {
                            return { revision: Math.floor(Date.now() / 1000) }
                        },
                        revisions: function() {
                            return { revision: {} }
                        },
                        modified: function() {
                            return { modified: new Date() }
                        },
                        pending: function() {
                            return { pending: 0 }
                        }
                    }

                    for(var property in properties) {
                        if( property in current ) {
                            console.log('Verified property ' + property + ' of revision: ' + current._id)
                        } else {
                            //Update revision document with given property
                            var update = properties[property]()
                            updatePageRevisionProperty(current._id, update )
                        }
                    }
                }
            }

            db.close()
        })
    })
}

function updatePageRevisionProperty(id, object) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        db.collection('revisions').update({_id:id},{ $set: object }, function(err, result) {
            if (err) throw err

            console.log('Updated property ' + Object.keys(object) + ' for revision: ' + id)

            db.close()
        })
    })
}

function insertPageRevision(page) {
    client.connect(config.database.host+config.database.name, function(err, db){
        if (err) throw err

        var timestamp = ( new Date(page.timestamp.created) / 1000 ) //Unix timestamp
        var revision = {
            post_id: page._id,
            pending: 0,
            modified: "2016-02-03T22:22:12Z",
            revision: timestamp,
            revisions: {}
        }

        revision.revisions[timestamp] = page.post
        revision.revisions[timestamp].meta = {
            accepted: true, //always accept current page version
            user_info: page.user_info,
            timestamp: page.timestamp
        }

        db.collection('revisions').insert(revision, function(err, result) {
            if (err) throw err

            if(result.insertedCount > 0) {
                console.log('Skapade revision document för sida ' + page.title)
            }

            db.close()
        })
    })
}

/* Check for collections */
client.connect(config.database.host+config.database.name, function(err, db){
    if(err) throw err

    db.listCollections().toArray(function(err, dbCollections) {
        if(err) throw err

        for (var collection in collections) {
            if (collections.hasOwnProperty(collection)) {
                for (var dbCollection in dbCollections) {
                    if (dbCollections.hasOwnProperty(dbCollection)) {
                        if(dbCollections[dbCollection].hasOwnProperty('name')) {
                            if(dbCollections[dbCollection].name == collections[collection].name) {
                                collections[collection].exists = true
                            }
                        }
                    }
                }
            }
        }

        /* Create collections that don't exist */
        createCollections = []

        for (var collection in collections) {
            if (collections.hasOwnProperty(collection)) {
                if(collections[collection].exists == false) {
                    createCollection(collections[collection].name)

                    if(collections[collection].hasOwnProperty('data')) {
                        insertDataToCollection(collections[collection].name, collections[collection].data)
                    }
                } else {
                    //Check that all data in that collection is inserted
                    if(collections[collection].hasOwnProperty('data')) {
                        //Loop over data
                        verifyDataInCollection(collections[collection].name, collections[collection].data)
                    }
                }
            }
        }

        /* Create revision document for pages that don't have one */
        db.collection('pages').find({}).toArray(function(err, docs){
            if (err) throw err
            if(docs.length > 0) {
                for (var i = docs.length - 1; i >= 0; i--) {
                    verifyPageRevision(docs[i])
                }

                db.close()
            }
        })
    })

    process.on('exit', function() {
        console.log('*** Configure done! ***')
    });
})
<|endoftext|>


<commit_before>// Ripple custom JS that goes on all pages

var singlePageSnippets = {

  "/": function() {
    $(".expand-icon").popup().click(function() {
      var addTo = $(this).closest(".segment");
      if (addTo.attr("data-expanded") == "true") {
        addTo.removeAttr("data-expanded")
        addTo.children(".post-content").slideUp();
        $(this).attr("data-content", "Load post inline")
        $(this).removeClass("up").addClass("down");
      } else {
        addTo.attr("data-expanded", "true");
        $(this).removeClass("down").addClass("up");
        $(this).attr("data-content", "Reduce");
        api("blog/posts/content", {
          id: addTo.data("post-id"),
          html: "",
        }, function(data) {
          var el = $("
").hide().append(data.content);
          addTo.append(el);
          el.slideDown();
        });
      }
    });
  },

  "/login": function() {
    $("#login-form").submit(function(e) {
      $("button").addClass("disabled");

      var fix = function(errorMessage) {
        $("button").removeClass("disabled");
        $(".ui.form").removeClass("loading");
        showMessage("error", errorMessage);
      };

      if (!/^[a-zA-Z0-9 \[\]\@\.\+-]+$/.test($("input[name='username']").val())) {
        fix("Invalid username.");
        return false;
      }
    });
  },

  "/settings/avatar": function() {
    // TODO
    // https://foliotek.github.io/Croppie/demo/demo.js
    $uploadCrop = $('#croppie-thing').croppie({
      enableExif: true,
      viewport: {
        width: 128,
        height: 128
      },
      boundary: {
        width: 300,
        height: 300
      }
    });
  }
};

$(document).ready(function(){
  // semantic stuff
  $('.message .close').on('click', closeClosestMessage);

  $('.ui.checkbox').checkbox();

  $('.ui.form').submit(function() {
    $(this).addClass("loading");
  });

  $('.ui.dropdown').dropdown();

  $('.ui.progress').progress();

  // ripple stuff
  var f = singlePageSnippets[window.location.pathname];
  if (typeof f === 'function')
    f();

  if (window.location.pathname.substr(0, 3) == "/u/")
    userProfile();

  $("time.timeago").timeago();
});

var closeClosestMessage = function() {
  $(this)
    .closest('.message')
    .transition('fade');
};

var showMessage = function(type, message) {
  var newEl = $('
' + message + '
');
  newEl.find(".close.icon").click(closeClosestMessage);
  $("#messages-container").append(newEl);
  newEl.transition("slide down");
};

// function for all api calls
var api = function(endpoint, data, success) {
  if (typeof data == "function") {
    success = data;
    data = null;
  }

  var errorMessage = "An error occurred while contacting the Ripple API. Please report this to a Ripple developer.";

  $.ajax({
    dataType: "json",
    url:      "/api/v1/" + endpoint,
    data:     data,
    success:  function(data) {
      if (data.code != 200) {
        console.warn(data);
        showMessage("error", errorMessage);
      }
      success(data);
    },
    error:    function(jqXHR, textStatus, errorThrown) {
      console.warn(jqXHR, textStatus, errorThrown);
      showMessage("error", errorMessage);
    },
  });
};

var userProfile = function() {
  var wl = window.location;
  if (wl.search.indexOf("mode=") === -1)
    window.history.pushState('', document.title, wl.pathname + "?mode=" + favouriteMode + wl.hash);
  $("#mode-menu>.item").click(function() {
    if ($(this).hasClass("active"))
      return;
    $("[data-mode]:not(.item):not([hidden])").attr("hidden", "");
    $("[data-mode=" + $(this).data("mode") + "]:not(.item)").removeAttr("hidden");
    $("#mode-menu>.active.item").removeClass("active");
    $(this).addClass("active");
    window.history.pushState('', document.title, wl.pathname + "?mode=" + $(this).data("mode") + wl.hash);
  });
};
<commit_msg>Allow future in moment.js<commit_after>// Ripple custom JS that goes on all pages

var singlePageSnippets = {

  "/": function() {
    $(".expand-icon").popup().click(function() {
      var addTo = $(this).closest(".segment");
      if (addTo.attr("data-expanded") == "true") {
        addTo.removeAttr("data-expanded")
        addTo.children(".post-content").slideUp();
        $(this).attr("data-content", "Load post inline")
        $(this).removeClass("up").addClass("down");
      } else {
        addTo.attr("data-expanded", "true");
        $(this).removeClass("down").addClass("up");
        $(this).attr("data-content", "Reduce");
        api("blog/posts/content", {
          id: addTo.data("post-id"),
          html: "",
        }, function(data) {
          var el = $("
").hide().append(data.content);
          addTo.append(el);
          el.slideDown();
        });
      }
    });
  },

  "/login": function() {
    $("#login-form").submit(function(e) {
      $("button").addClass("disabled");

      var fix = function(errorMessage) {
        $("button").removeClass("disabled");
        $(".ui.form").removeClass("loading");
        showMessage("error", errorMessage);
      };

      if (!/^[a-zA-Z0-9 \[\]\@\.\+-]+$/.test($("input[name='username']").val())) {
        fix("Invalid username.");
        return false;
      }
    });
  },

  "/settings/avatar": function() {
    // TODO
    // https://foliotek.github.io/Croppie/demo/demo.js
    $uploadCrop = $('#croppie-thing').croppie({
      enableExif: true,
      viewport: {
        width: 128,
        height: 128
      },
      boundary: {
        width: 300,
        height: 300
      }
    });
  }
};

$(document).ready(function(){
  // semantic stuff
  $('.message .close').on('click', closeClosestMessage);

  $('.ui.checkbox').checkbox();

  $('.ui.form').submit(function() {
    $(this).addClass("loading");
  });

  $('.ui.dropdown').dropdown();

  $('.ui.progress').progress();

  // ripple stuff
  var f = singlePageSnippets[window.location.pathname];
  if (typeof f === 'function')
    f();

  if (window.location.pathname.substr(0, 3) == "/u/")
    userProfile();

  $.timeago.settings.allowFuture = true;
  $("time.timeago").timeago();
});

var closeClosestMessage = function() {
  $(this)
    .closest('.message')
    .transition('fade');
};

var showMessage = function(type, message) {
  var newEl = $('
' + message + '
');
  newEl.find(".close.icon").click(closeClosestMessage);
  $("#messages-container").append(newEl);
  newEl.transition("slide down");
};

// function for all api calls
var api = function(endpoint, data, success) {
  if (typeof data == "function") {
    success = data;
    data = null;
  }

  var errorMessage = "An error occurred while contacting the Ripple API. Please report this to a Ripple developer.";

  $.ajax({
    dataType: "json",
    url:      "/api/v1/" + endpoint,
    data:     data,
    success:  function(data) {
      if (data.code != 200) {
        console.warn(data);
        showMessage("error", errorMessage);
      }
      success(data);
    },
    error:    function(jqXHR, textStatus, errorThrown) {
      console.warn(jqXHR, textStatus, errorThrown);
      showMessage("error", errorMessage);
    },
  });
};

var userProfile = function() {
  var wl = window.location;
  if (wl.search.indexOf("mode=") === -1)
    window.history.pushState('', document.title, wl.pathname + "?mode=" + favouriteMode + wl.hash);
  $("#mode-menu>.item").click(function() {
    if ($(this).hasClass("active"))
      return;
    $("[data-mode]:not(.item):not([hidden])").attr("hidden", "");
    $("[data-mode=" + $(this).data("mode") + "]:not(.item)").removeAttr("hidden");
    $("#mode-menu>.active.item").removeClass("active");
    $(this).addClass("active");
    window.history.pushState('', document.title, wl.pathname + "?mode=" + $(this).data("mode") + wl.hash);
  });
};
<|endoftext|>


<commit_before>import React from "react";
import PropTypes from "prop-types";
import { Column, Hide, Row } from "@agir/front/genericComponents/grid";
import { DateTime } from "luxon";
import Button from "@agir/front/genericComponents/Button";

const EventDescription = ({
  compteRendu,
  compteRenduPhotos,
  illustration,
  description,
  isOrganizer,
  endTime,
  rsvp,
  routes,
}) => (
  <>
    {(!!compteRendu || compteRenduPhotos.length > 0) && (

Compte-rendu

    )}
    {compteRenduPhotos.length > 0 && (
      <Row gutter={12}>
        {compteRenduPhotos.map((url, key) => (
          <Column collapse={500} key={key} width={["50%", "content"]}>

          </Column>
        ))}
      </Row>
    )}
    {endTime < DateTime.local() && isOrganizer && (
      <Button
        as="a"
        color="primary"
        href={routes.compteRendu}
        style={{ margin: "1em 1em 0 0" }}
      >
        {compteRendu ? "Modifier le" : "Ajouter un"} compte-rendu
      </Button>
    )}
    {endTime < DateTime.local() && !!rsvp && (
      <Button as="a" href={routes.addPhoto} style={{ marginTop: "1em" }}>
        Ajouter une photo
      </Button>
    )}
    {compteRendu && (

    )}
    {description &&
L'événement
}
    <Hide under>
      {illustration && (

      )}
    </Hide>

  </>
);

EventDescription.propTypes = {
  compteRendu: PropTypes.string,
  compteRenduPhotos: PropTypes.arrayOf(PropTypes.string),
  illustration: PropTypes.string,
  description: PropTypes.string,
  isOrganizer: PropTypes.bool,
  endTime: PropTypes.instanceOf(DateTime),
  routes: PropTypes.object,
  rsvp: PropTypes.string,
};

export default EventDescription;
<commit_msg>feat(events): cta description<commit_after>import React from "react";
import PropTypes from "prop-types";
import { Column, Hide, Row } from "@agir/front/genericComponents/grid";
import { DateTime } from "luxon";
import Button from "@agir/front/genericComponents/Button";

const EventDescription = ({
  compteRendu,
  compteRenduPhotos,
  illustration,
  description,
  isOrganizer,
  endTime,
  rsvp,
  routes,
}) => (
  <>
    {(!!compteRendu || compteRenduPhotos.length > 0) && (

Compte-rendu

    )}
    {compteRenduPhotos.length > 0 && (
      <Row gutter={12}>
        {compteRenduPhotos.map((url, key) => (
          <Column collapse={500} key={key} width={["50%", "content"]}>

          </Column>
        ))}
      </Row>
    )}
    {endTime < DateTime.local() && isOrganizer && (
      <Button
        as="a"
        color="primary"
        href={routes.compteRendu}
        style={{ margin: "1em 1em 0 0" }}
      >
        {compteRendu ? "Modifier le" : "Ajouter un"} compte-rendu
      </Button>
    )}
    {endTime < DateTime.local() && !!rsvp && (
      <Button as="a" href={routes.addPhoto} style={{ marginTop: "1em" }}>
        Ajouter une photo
      </Button>
    )}
    {compteRendu && (

    )}
    {description &&
L'événement
}
    <Hide under>
      {illustration && (

      )}
    </Hide>
    {description ? (

    ) : (
      isOrganizer && (
        <>

Ajoutez une description !

            Donner tous les informations nécessaires aux participants de votre
            événement. Comment accéder au lieu, quel est le programme, les liens
            pour être tenu au courant... Et ajoutez une image !

          <Button as="a" color="primary" href={routes.manage}>
            Ajouter une description
          </Button>
        </>
      )
    )}
  </>
);

EventDescription.propTypes = {
  compteRendu: PropTypes.string,
  compteRenduPhotos: PropTypes.arrayOf(PropTypes.string),
  illustration: PropTypes.string,
  description: PropTypes.string,
  isOrganizer: PropTypes.bool,
  endTime: PropTypes.instanceOf(DateTime),
  routes: PropTypes.object,
  rsvp: PropTypes.string,
};

export default EventDescription;
<|endoftext|>


<commit_before>/*
 * deferred-exec
 * https://github.com/danheberden/deferred-exec
 *
 * Copyright (c) 2012 Dan Heberden
 * Licensed under the MIT license.
 */
var child_process = require( 'child_process' );
var _ = require( 'lodash' );
_.mixin( require( 'underscore.deferred' ) );
_.mixin( require( 'underscore.string' ).exports() );

var deferredorator = function( type, command, args, options ) {
  options = options || {};
  args = args || [];

  //reduce the fn signature
  if ( type === 'exec' ) {
    options = args;
  }

  var dfd = _.Deferred();
  var trim = _.rtrim;

  if ( options.trim === false ) {
    var trim = function(a){ return a; };
  }

  var hollaback = function( error, stdout, stderr ) {
    if ( error !== null ) {
      dfd.reject( error );
    } else {
      dfd.resolve( trim( stdout ), trim( stderr ), command );
    }
  };

  if ( type === 'file' ) {
    child_process.execFile( command, args, options, hollaback );
  }

  if ( type === 'exec' ) {
    child_process.exec( command, hollaback );
  }

  if ( type === 'spawn' ) {
    var cp = child_process.spawn( command, args, options );
    // set encodings
    cp.stdout.setEncoding( options.encoding || 'utf8' );
    cp.stderr.setEncoding( options.encoding || 'utf8' );
    var stdout = '';
    var stderr = '';

    // new stdio api introduced the exit event not waiting for open pipes
    var eventType = cp.stdio ? 'close' : 'exit';

    cp.stdout.on( 'data', function( data ) {
      stdout += data;
      dfd.notify( data, '', command );
    });

    cp.stderr.on( 'data', function( data ) {
      stderr += data;
      dfd.notify( '', data, command );
    });

    cp.on( eventType, function( code ) {
      if ( code !== 0 ) {
        var error = new Error( command + " failed" );
        error.code = code;
        dfd.reject( error );
      } else {
        dfd.resolve( trim( stdout ), trim( stderr ), command );
      }
    });
  }

  return dfd.promise();
};

module.exports = function( command, options ) {
  return deferredorator( 'exec', command, options );
};
module.exports.file = function( command, args, options ) {
  return deferredorator( 'file', command, args, options );
};
module.exports.spawn = function( command, args, options ) {
  return deferredorator( 'spawn', command, args, options );
};
<commit_msg>add stdout to rejection handler<commit_after>/*
 * deferred-exec
 * https://github.com/danheberden/deferred-exec
 *
 * Copyright (c) 2012 Dan Heberden
 * Licensed under the MIT license.
 */
var child_process = require( 'child_process' );
var _ = require( 'lodash' );
_.mixin( require( 'underscore.deferred' ) );
_.mixin( require( 'underscore.string' ).exports() );

var deferredChildProcess = function( type, command, args, options ) {
  options = options || {};
  args = args || [];

  //reduce the fn signature
  if ( type === 'exec' ) {
    options = args;
  }

  var dfd = _.Deferred();
  var trim = _.rtrim;

  if ( options.trim === false ) {
    var trim = function(a){ return a; };
  }

  var hollaback = function( error, stdout, stderr ) {
    if ( error !== null ) {
      dfd.reject( error, stdout, stderr, command );
    } else {
      dfd.resolve( trim( stdout ), trim( stderr ), command );
    }
  };

  if ( type === 'file' ) {
    child_process.execFile( command, args, options, hollaback );
  }

  if ( type === 'exec' ) {
    child_process.exec( command, hollaback );
  }

  if ( type === 'spawn' ) {
    var cp = child_process.spawn( command, args, options );
    // set encodings
    cp.stdout.setEncoding( options.encoding || 'utf8' );
    cp.stderr.setEncoding( options.encoding || 'utf8' );
    var stdout = '';
    var stderr = '';

    // new stdio api introduced the exit event not waiting for open pipes
    var eventType = cp.stdio ? 'close' : 'exit';

    cp.stdout.on( 'data', function( data ) {
      stdout += data;
      dfd.notify( data, '', command );
    });

    cp.stderr.on( 'data', function( data ) {
      stderr += data;
      dfd.notify( '', data, command );
    });

    cp.on( eventType, function( code ) {
      if ( code !== 0 ) {
        var error = new Error( command + " failed" );
        error.code = code;
        dfd.reject( error );
      } else {
        dfd.resolve( trim( stdout ), trim( stderr ), command );
      }
    });
  }

  return dfd.promise();
};

module.exports = function( command, options ) {
  return deferredChildProcess( 'exec', command, options );
};
module.exports.file = function( command, args, options ) {
  return deferredChildProcess( 'file', command, args, options );
};
module.exports.spawn = function( command, args, options ) {
  return deferredChildProcess( 'spawn', command, args, options );
};
module.exports.prompt = function(){};
<|endoftext|>


<commit_before>/* App.Models.MapModel.js
 * ================
 *
 * MapModel holds the data and underlying logic needed by MapTab.
 * It wraps the map-config property on ChartModel.
 *
 * @project Our World In Data
 * @author  Jaiden Mispy
 * @created 2016-08-08
 */

;(function() {
	"use strict";
	owid.namespace("App.Models.MapModel");

	App.Models.MapModel = Backbone.Model.extend({
		defaults: {
			"variableId": -1,
			"targetYear": 1980,
			"targetYearMode": "latest",
			"defaultYear": 1980,
			"mode": "specific",
			"timeTolerance": 1,
			// timeRanges is a collection of objects specifying year ranges e.g.
			//
			// [
			//   { year: 1980 },
			//   { startYear: 1990, endYear: 2000, interval: 5 },
			//   { startYear: 2005, endYear: 2008 }
			// ]
			//
			// Produces the years: 1980, 1990, 1995, 2000, 2005, 2007, 2008
			"timeRanges": [],
			"timelineMode": "slider",
			// Name of an owid.colorbrewer scheme, may then be further customized
			"baseColorScheme": "BuGn",
			// Number of numeric intervals used to color data
			"colorSchemeInterval": 10,
			// Minimum value shown on map legend
			"colorSchemeMinValue": "",
			"colorSchemeValues": [],
			"colorSchemeLabels": [],
			"colorSchemeValuesAutomatic": true,
			// Whether to reverse the color scheme on output
			"colorSchemeInvert": false,
			"customColorsActive": false,
			// e.g. ["#000", "#c00", "#0c0", "#00c", "#c0c"]
			"customNumericColors": [],
			// e.g. { 'foo' => '#c00' }
			"customCategoryColors": {},
			"customCategoryLabels": {},
			// Allow hiding categories from the legend
			"customHiddenCategories": {},
			"isColorblind": false,
			"projection": "World",
			"defaultProjection": "World",
			"legendDescription": "",
			"legendStepSize": 20,
			"legendOrientation": "portrait",
		},

		bind: function(chartModel) {
			this.chartModel = chartModel;

			this.listenTo(this.chartModel, "change:map-config", this.loadConfig.bind(this));
			this.loadConfig();

			this.on("change", function() {
				this.chartModel.set("map-config", this.toJSON(), { silent: true });
			}.bind(this));

			// Ensure number of colors matches custom color scheme array length
			this.on("change:colorSchemeInterval", function() {
				var colorSchemeName = this.get("colorSchemeName"),
					customColorScheme = _.clone(this.get("customColorScheme")),
					colorSchemeInterval = this.get("colorSchemeInterval");

				if (colorSchemeName == "custom") {
					if (colorSchemeInterval < customColorScheme.length)
						this.set("customColorScheme", customColorScheme.slice(0, colorSchemeInterval));
					else if (colorSchemeInterval > customColorScheme.length) {
						for (var i = customColorScheme.length; i < colorSchemeInterval; i++) {
							customColorScheme.push("#ffffff");
						}
						this.set("customColorScheme", customColorScheme);
					}
				}
			}.bind(this));

			this.on("change:variableId change:timeRanges", this.updateYears.bind(this));
			this.listenTo(App.VariableData, "change:minYear change:maxYear", this.updateYears.bind(this));
		},

		loadConfig: function() {
			var mapConfig = this.chartModel.get("map-config");
			if (!mapConfig) {
				this.chartModel.set("map-config", this.defaults);
				return;
			}

			// Ensure everything that is a number should be
			_.each(this.defaults, function(defaultVal, k) {
				var val = mapConfig[k];
				if (_.isNumber(defaultVal) && _.isString(val))
					mapConfig[k] = +val;
			});

			this.set(mapConfig);

			this.set('targetYear', this.get('defaultYear'));
		},

		updateYears: function() {
			var timeRanges = this.get("timeRanges"),
				minVariableYear = App.VariableData.get("minYear"),
				maxVariableYear = App.VariableData.get("maxYear"),
				defaultYear = this.get("defaultYear"),
				targetYear = this.get("targetYear");

			this.years = owid.timeRangesToYears(timeRanges, minVariableYear, maxVariableYear);

			var minYear = this.getMinYear(),
				maxYear = this.getMaxYear();

			// Sanity check our target years
			if (defaultYear < minYear)
				this.set("defaultYear", minYear);
			else if (defaultYear > maxYear)
				this.set("defaultYear", maxYear);

			if (targetYear < minYear)
				this.set("targetYear", minYear);
			else if (targetYear > maxYear)
				this.set("targetYear", maxYear);

			// Update min and max values
			var variable = this.getVariable();
			this.minValue = Infinity;
			this.maxValue = -Infinity;
			if (variable && variable.isNumeric) {
				_.each(variable.values, function(value, i) {
					var year = variable.years[i];
					if (_.contains(this.years, year)) {
						var entity = App.VariableData.getEntityById(variable.entities[i]);

						// If there's a World or other meta entity, it can throw off the map calcs
						if (entity.code && !s.startsWith(entity.code, "OWID")) {
							if (value < this.minValue) this.minValue = value;
							if (value > this.maxValue) this.maxValue = value;
						}
					}
				}.bind(this));
			}
		},

		getYears: function() {
			return this.years;
		},

		getMinYear: function() {
			return this.years[0];
		},

		getMaxYear: function() {
			return this.years[this.years.length-1];
		},

		getMinValue: function() {
			return this.minValue;
		},

		getMaxValue: function() {
			return this.maxValue;
		},

		getVariable: function() {
			return App.VariableData.getVariableById(this.get("variableId"));
		},

		getLegendMin: function() {
			var minValue = this.get('colorSchemeMinValue');
			if (parseFloat(minValue) != minValue) return -Infinity;
			else return +minValue;
		},

		// Generate colors for map
		// If color scheme is customized, we simply return the stored custom data
		getColors: function(mapConfig) {
			mapConfig = mapConfig || _.clone(this.attributes);
			var	variable = App.MapModel.getVariable(),
				hasNumeric = variable.hasNumericValues,
				hasCategorical = variable.hasCategoricalValues,
				isOnlyNumeric = hasNumeric && !hasCategorical,
				isColorblind = mapConfig.isColorblind,
				colorSchemeName = (isColorblind ? "RdYlBu" : mapConfig.colorSchemeName) || "",
				colorSchemeInterval = mapConfig.colorSchemeInterval || 2,
				colorSchemeInvert = mapConfig.colorSchemeInvert || false,
				customColorScheme = mapConfig.customColorScheme || [],
				numColors = isOnlyNumeric ? colorSchemeInterval : variable.categoricalValues.length;

			if (colorSchemeInvert) {
				var colors = this.getColors(_.extend({}, mapConfig, { colorSchemeInvert: false }));
				return colors.reverse();
			}

			if (colorSchemeName === "custom")
				return _.clone(customColorScheme);

			var scheme = owid.colorbrewer[colorSchemeName];
			if (!scheme) {
				console.error("No such color scheme: " + scheme);
				// Return a default color scheme
				return this.getColors(_.extend({}, mapConfig, { colorSchemeName: _.keys(owid.colorbrewer)[0] }));
			}

			if (!_.isEmpty(scheme.colors[numColors]))
				return _.clone(scheme.colors[numColors]);

			// Handle the case of a single color (just for completeness' sake)
			if (numColors == 1 && !_.isEmpty(scheme.colors[2]))
				return [scheme.colors[2][0]];

			// If there's no preset color scheme for this many colors, improvise a new one
			var colors = _.clone(scheme.colors[scheme.colors.length-1]);
			while (colors.length < numColors) {
				for (var i = 1; i < colors.length; i++) {
					var startColor = d3.rgb(colors[i-1]);
					var endColor = d3.rgb(colors[i]);
					var newColor = d3.interpolate(startColor, endColor)(0.5);
					colors.splice(i, 0, newColor);
					i += 1;

					if (colors.length >= numColors) break;
				}
			}
			return colors;
		},
	});
})();<commit_msg>Always set target year when default changes<commit_after>/* App.Models.MapModel.js
 * ================
 *
 * MapModel holds the data and underlying logic needed by MapTab.
 * It wraps the map-config property on ChartModel.
 *
 * @project Our World In Data
 * @author  Jaiden Mispy
 * @created 2016-08-08
 */

;(function() {
	"use strict";
	owid.namespace("App.Models.MapModel");

	App.Models.MapModel = Backbone.Model.extend({
		defaults: {
			"variableId": -1,
			"targetYear": 1980,
			"targetYearMode": "latest",
			"defaultYear": 1980,
			"mode": "specific",
			"timeTolerance": 1,
			// timeRanges is a collection of objects specifying year ranges e.g.
			//
			// [
			//   { year: 1980 },
			//   { startYear: 1990, endYear: 2000, interval: 5 },
			//   { startYear: 2005, endYear: 2008 }
			// ]
			//
			// Produces the years: 1980, 1990, 1995, 2000, 2005, 2007, 2008
			"timeRanges": [],
			"timelineMode": "slider",
			// Name of an owid.colorbrewer scheme, may then be further customized
			"baseColorScheme": "BuGn",
			// Number of numeric intervals used to color data
			"colorSchemeInterval": 10,
			// Minimum value shown on map legend
			"colorSchemeMinValue": "",
			"colorSchemeValues": [],
			"colorSchemeLabels": [],
			"colorSchemeValuesAutomatic": true,
			// Whether to reverse the color scheme on output
			"colorSchemeInvert": false,
			"customColorsActive": false,
			// e.g. ["#000", "#c00", "#0c0", "#00c", "#c0c"]
			"customNumericColors": [],
			// e.g. { 'foo' => '#c00' }
			"customCategoryColors": {},
			"customCategoryLabels": {},
			// Allow hiding categories from the legend
			"customHiddenCategories": {},
			"isColorblind": false,
			"projection": "World",
			"defaultProjection": "World",
			"legendDescription": "",
			"legendStepSize": 20,
			"legendOrientation": "portrait",
		},

		bind: function(chartModel) {
			this.chartModel = chartModel;

			this.listenTo(this.chartModel, "change:map-config", this.loadConfig.bind(this));
			this.loadConfig();

			this.on("change", function() {
				this.chartModel.set("map-config", this.toJSON(), { silent: true });
			}.bind(this));

			// Ensure number of colors matches custom color scheme array length
			this.on("change:colorSchemeInterval", function() {
				var colorSchemeName = this.get("colorSchemeName"),
					customColorScheme = _.clone(this.get("customColorScheme")),
					colorSchemeInterval = this.get("colorSchemeInterval");

				if (colorSchemeName == "custom") {
					if (colorSchemeInterval < customColorScheme.length)
						this.set("customColorScheme", customColorScheme.slice(0, colorSchemeInterval));
					else if (colorSchemeInterval > customColorScheme.length) {
						for (var i = customColorScheme.length; i < colorSchemeInterval; i++) {
							customColorScheme.push("#ffffff");
						}
						this.set("customColorScheme", customColorScheme);
					}
				}
			}.bind(this));

			this.on("change:variableId change:timeRanges", this.updateYears.bind(this));
			this.listenTo(App.VariableData, "change:minYear change:maxYear", this.updateYears.bind(this));

			this.on("change:defaultYear", function() {
				this.set('targetYear', this.get('defaultYear'));
			});
		},

		loadConfig: function() {
			var mapConfig = this.chartModel.get("map-config");
			if (!mapConfig) {
				this.chartModel.set("map-config", this.defaults);
				return;
			}

			// Ensure everything that is a number should be
			_.each(this.defaults, function(defaultVal, k) {
				var val = mapConfig[k];
				if (_.isNumber(defaultVal) && _.isString(val))
					mapConfig[k] = +val;
			});

			this.set(mapConfig);
		},

		updateYears: function() {
			var timeRanges = this.get("timeRanges"),
				minVariableYear = App.VariableData.get("minYear"),
				maxVariableYear = App.VariableData.get("maxYear"),
				defaultYear = this.get("defaultYear"),
				targetYear = this.get("targetYear");

			this.years = owid.timeRangesToYears(timeRanges, minVariableYear, maxVariableYear);

			var minYear = this.getMinYear(),
				maxYear = this.getMaxYear();

			// Sanity check our target years
			if (defaultYear < minYear)
				this.set("defaultYear", minYear);
			else if (defaultYear > maxYear)
				this.set("defaultYear", maxYear);

			if (targetYear < minYear)
				this.set("targetYear", minYear);
			else if (targetYear > maxYear)
				this.set("targetYear", maxYear);

			// Update min and max values
			var variable = this.getVariable();
			this.minValue = Infinity;
			this.maxValue = -Infinity;
			if (variable && variable.isNumeric) {
				_.each(variable.values, function(value, i) {
					var year = variable.years[i];
					if (_.contains(this.years, year)) {
						var entity = App.VariableData.getEntityById(variable.entities[i]);

						// If there's a World or other meta entity, it can throw off the map calcs
						if (entity.code && !s.startsWith(entity.code, "OWID")) {
							if (value < this.minValue) this.minValue = value;
							if (value > this.maxValue) this.maxValue = value;
						}
					}
				}.bind(this));
			}
		},

		getYears: function() {
			return this.years;
		},

		getMinYear: function() {
			return this.years[0];
		},

		getMaxYear: function() {
			return this.years[this.years.length-1];
		},

		getMinValue: function() {
			return this.minValue;
		},

		getMaxValue: function() {
			return this.maxValue;
		},

		getVariable: function() {
			return App.VariableData.getVariableById(this.get("variableId"));
		},

		getLegendMin: function() {
			var minValue = this.get('colorSchemeMinValue');
			if (parseFloat(minValue) != minValue) return -Infinity;
			else return +minValue;
		},

		// Generate colors for map
		// If color scheme is customized, we simply return the stored custom data
		getColors: function(mapConfig) {
			mapConfig = mapConfig || _.clone(this.attributes);
			var	variable = App.MapModel.getVariable(),
				hasNumeric = variable.hasNumericValues,
				hasCategorical = variable.hasCategoricalValues,
				isOnlyNumeric = hasNumeric && !hasCategorical,
				isColorblind = mapConfig.isColorblind,
				colorSchemeName = (isColorblind ? "RdYlBu" : mapConfig.colorSchemeName) || "",
				colorSchemeInterval = mapConfig.colorSchemeInterval || 2,
				colorSchemeInvert = mapConfig.colorSchemeInvert || false,
				customColorScheme = mapConfig.customColorScheme || [],
				numColors = isOnlyNumeric ? colorSchemeInterval : variable.categoricalValues.length;

			if (colorSchemeInvert) {
				var colors = this.getColors(_.extend({}, mapConfig, { colorSchemeInvert: false }));
				return colors.reverse();
			}

			if (colorSchemeName === "custom")
				return _.clone(customColorScheme);

			var scheme = owid.colorbrewer[colorSchemeName];
			if (!scheme) {
				console.error("No such color scheme: " + scheme);
				// Return a default color scheme
				return this.getColors(_.extend({}, mapConfig, { colorSchemeName: _.keys(owid.colorbrewer)[0] }));
			}

			if (!_.isEmpty(scheme.colors[numColors]))
				return _.clone(scheme.colors[numColors]);

			// Handle the case of a single color (just for completeness' sake)
			if (numColors == 1 && !_.isEmpty(scheme.colors[2]))
				return [scheme.colors[2][0]];

			// If there's no preset color scheme for this many colors, improvise a new one
			var colors = _.clone(scheme.colors[scheme.colors.length-1]);
			while (colors.length < numColors) {
				for (var i = 1; i < colors.length; i++) {
					var startColor = d3.rgb(colors[i-1]);
					var endColor = d3.rgb(colors[i]);
					var newColor = d3.interpolate(startColor, endColor)(0.5);
					colors.splice(i, 0, newColor);
					i += 1;

					if (colors.length >= numColors) break;
				}
			}
			return colors;
		},
	});
})();<|endoftext|>


<commit_before>// Declarations

var coords, map, updater, live;

const fadeTime = 250,
	  updateTime = 2000;

// Functions (Stops)

function getParameter(name) {
	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}

function getSecondsSinceMidnight() {
	return ~~((new Date() - new Date().setHours(0, 0, 0, 0)) / 1000);
}

function toHMS(seconds) {
	var hours   = Math.floor(seconds / 3600),
		minutes = Math.floor((seconds % 3600) / 60);
	seconds = seconds - (minutes * 60) - (hours * 3600);
	return { h: hours, m: minutes, s: seconds };
}

function toCountdown(seconds, showHours) {
	seconds = seconds - getSecondsSinceMidnight();
	var time = toHMS(Math.abs(seconds));
	return (seconds < 0 ? '-' : '') + (showHours && time.h !== 0 ? time.h + 'h ' : '') + (time.m !== 0 ? time.h !== 0 && time.m < 10 ? ('0' + time.m).slice(-2) + 'm ' : time.m + 'm ' : '') + (showHours && time.h === 0 && time.m === 0 || !showHours ? (time.m !== 0 ? ('0' + time.s).slice(-2) : time.s) + 's' : '');
}

function toTime(seconds) {
	var time = toHMS(seconds);
	return ('0' + time.h).slice(-2) + ':' + ('0' + time.m).slice(-2);
}

function showStop(id, settings) {
	if (updater && !live) {

		$('.trip-scheduled').each(function() {
			$(this).text(toCountdown($(this).attr('data-time')));

			if ($(this).attr('data-time') < getSecondsSinceMidnight()) $(this).parent().remove();
		});

		return;
	}

	$.get('//' + location.host + '/getstop?id=' + id).done(function(stop) {
		hideBookmarks();

		var trips   = stop.trips,
			content = '';

		live = stop.live;

		for (var i = 0; i < trips.length; i++) {
			var trip = trips[i];

			if (live) {
				content += '

' + trip.number + '

' + toCountdown(trip.scheduled) + '

' + toCountdown(trip.expected) + '

';
			} else {
				content += '

' + trip.number + '

' + toCountdown(trip.time) + '

' + toTime(trip.time) + '

';
			}

		}
		$('#stop-trips').html(content);

		if (settings.panMap) map.panTo({
			lat: stop.lat,
			lng: stop.lng
		});

		if (!settings.fadeIn) return;

		document.title = 'Bussiaeg - ' + stop.name + (stop.desc ? ' - ' + stop.desc : '');
		history.pushState(null, document.title, '/?stop=' + stop.id);

		$('#stop-name').text(stop.name);
		$('#stop-desc').text(stop.desc);

		$('#stop').fadeIn(fadeTime);
	});

}

function showStops() {

	if (map.getZoom() <= 15) return map.eachLayer(function(layer) {
		if (layer._icon !== undefined) map.removeLayer(layer);
	});

	var bounds = map.getBounds();
	$.get('//' + location.host + '/getstops?lat_min=' + bounds._northEast.lat + '&lng_min=' + bounds._northEast.lng + '&lat_max=' + bounds._southWest.lat + '&lng_max=' + bounds._southWest.lng, function(stops) {

		for (var i = 0; i < stops.length; i++) {

			(function() {
				var stop = stops[i];

				L.marker([stop.lat, stop.lng], {
					icon: L.icon({
						iconUrl: 'assets/stop.png',
						iconSize: [24, 24],
						iconAnchor: [0, 0],
					})
				}).addTo(map).on('click', function() {
					if (updater) return;

					showStop(stop.id, {panMap: false, fadeIn: true});
					updater = setInterval(function() {
						showStop(stop.id, {panMap: false, fadeIn: false});
					}, updateTime);

				});

			})();

		}

	});

}

// Functions (Bookmarks)

function addBookmark() {

	swal({
		type: 'input',
		title: 'Sisesta nimi',
		confirmButtonColor: 'deepskyblue',
		confirmButtonText: 'Lisa',
		showCancelButton: true,
		cancelButtonColor: '#fa5858',
		cancelButtonText: 'Tagasi',
		inputPlaceholder: 'Minu kodu',
		animation: 'slide-from-top'
	}, function(input) {
		var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

		if (input !== false && $.trim(input) != '') bookmarks.push({
			name: input,
			lat:  map.getCenter().lat,
			lng:  map.getCenter().lng,
			zoom: map.getZoom()
		});

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));

		hideBookmarks();
	});

}

function deleteBookmark(el) {

	swal({
		title: 'Kustuta?',
		text: el.text(),
		confirmButtonText: 'Jah',
		cancelButtonText: 'Ei',
		showCancelButton: true
	}, function(isConfirm) {
		if (!isConfirm) return;

		var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));

		for (var i = 0; i < bookmarks.length; i++) {
			var bookmark = bookmarks[i];

			if (bookmark.lat != el.data('lat') || bookmark.lng != el.data('lng')) continue;

			bookmarks.splice(i, 1);
			el.remove();

			break;
		}

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
	});

}

function showBookmarks() {
	var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

	var content = '
Lisa asukoht
';
	for (var i = 0; i < bookmarks.length; i++) {
		var bookmark = bookmarks[i];

		content += '
' + bookmark.name + '
';

	}
	$('#bookmarks').html(content);

	$('.bookmark').each(function() {
		$(this).hammer().on('swiperight', function() {
			deleteBookmark($(this));
		});
	});

	$('#bookmarks').animate({left: '0px'}, fadeTime);
}

function hideBookmarks() {
	$('#bookmarks').animate({left: '-240px'}, fadeTime);
}

// Initialization

const share = parseInt(getParameter('stop'));
if (share) if (!isNaN(share)) if (!updater) {

	showStop(share, {panMap: true, fadeIn: true});
	updater = setInterval(function() {
		showStop(share, {panMap: false, fadeIn: false});
	}, updateTime);

}

var map = L.map('map', {
	center: [
		parseFloat(getParameter('lat')) || 59.388,
		parseFloat(getParameter('lng')) || 24.685
	],
	zoom: 16,
	minZoom: 10,
	maxZoom: 18,
	maxBounds: [[59.874204, 21.396935], [57.290822, 28.838625]],
	attributionControl: false,
	bounceAtZoomLimits: false,
	doubleClickZoom: false,
	zoomControl: false,
	keyboard: false,
	boxZoom: false
});

L.tileLayer('//{s}.google.com/vt/lyrs=phl=en&x={x}&y={y}&z={z}&pbhttps://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i372053562!3m14!2sen-US!3sUS!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2zcy50OjY2fHAudjpvZmY!4e0', {
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);

L.control.attribution({
	position: 'bottomleft',
	prefix: 'Bussiaeg.ee | Sõiduplaanid | Peatus | Google | Leaflet'
}).addTo(map);

showStops();
map.on('moveend', function() {
	showStops();
});

navigator.geolocation.getCurrentPosition(function(pos) {
	if (pos.coords.accuracy < 250) map.panTo([pos.coords.latitude, pos.coords.longitude]);
});

navigator.geolocation.watchPosition(function(pos) {
	coords = pos.coords;
});

// User Interface (Stops)

$('#stop-top').click(function() {
	clearInterval(updater); updater = null;

	$(this).parent().fadeOut(fadeTime);

	document.title = 'Bussiaeg';
	history.pushState(null, document.title, '/');

});

// User Interface (Bookmarks)

map.on('click dragstart', function() {
	$(this).addClass('bounce');
	hideBookmarks();
});

$('#bookmarks').hammer().on('swipeleft', function() {
	hideBookmarks();
});

$('#btn-bookmarks').click(function() {
	$(this).addClass('bounce');
	showBookmarks();
});

$('#bookmarks').on('click', '#bookmarks-add', function() {
	addBookmark();
});

$('#bookmarks').on('click', '.bookmark', function() {
	hideBookmarks();

	map.panTo([$(this).data('lat'), $(this).data('lng')]);
	map.setZoom($(this).data('zoom'));

});

// User Interface (Help)

$('#btn-help').click(function() {
	$(this).addClass('bounce');

	hideBookmarks();

	$('#help').fadeIn(fadeTime, function() {
		$('#btn-help').removeClass('bounce');
	});

});

$('#help').click(function() {
	$(this).fadeOut(fadeTime);
});

// User Interface (Locate)

$('#btn-locate').click(function() {
	$(this).addClass('bounce');
	if (coords) map.panTo([coords.latitude, coords.longitude]);
});

$('#btn-locate').on('animationend', function() {
	$(this).removeClass('bounce');
});<commit_msg>Remove showHours argument from toCountdown<commit_after>// Declarations

var coords, map, updater, live;

const fadeTime = 250,
	  updateTime = 2000;

// Functions (Stops)

function getParameter(name) {
	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}

function getSecondsSinceMidnight() {
	return ~~((new Date() - new Date().setHours(0, 0, 0, 0)) / 1000);
}

function toHMS(seconds) {
	var hours   = Math.floor(seconds / 3600),
		minutes = Math.floor((seconds % 3600) / 60);
	seconds = seconds - (minutes * 60) - (hours * 3600);
	return { h: hours, m: minutes, s: seconds };
}

function toCountdown(seconds) {
	seconds = seconds - getSecondsSinceMidnight();
	var time = toHMS(Math.abs(seconds));
	return (seconds < 0 ? '-' : '') + (!live && time.h !== 0 ? time.h + 'h ' : '') + (time.m !== 0 ? time.h !== 0 && time.m < 10 ? ('0' + time.m).slice(-2) + 'm ' : time.m + 'm ' : '') + (!live && time.h === 0 && time.m === 0 || live ? (time.m !== 0 ? ('0' + time.s).slice(-2) : time.s) + 's' : '');
}

function toTime(seconds) {
	var time = toHMS(seconds);
	return ('0' + time.h).slice(-2) + ':' + ('0' + time.m).slice(-2);
}

function showStop(id, settings) {
	if (updater && !live) {

		$('.trip-scheduled').each(function() {
			$(this).text(toCountdown($(this).attr('data-time')));

			if ($(this).attr('data-time') < getSecondsSinceMidnight()) $(this).parent().remove();
		});

		return;
	}

	$.get('//' + location.host + '/getstop?id=' + id).done(function(stop) {hideBookmarks();
		var trips   = stop.trips,
			content = '';

		live = stop.live;

		for (var i = 0; i < trips.length; i++) {
			var trip = trips[i];

			if (live) {
				content += '

' + trip.number + '

' + toCountdown(trip.scheduled) + '

' + toCountdown(trip.expected) + '

';
			} else {
				content += '

' + trip.number + '

' + toCountdown(trip.time) + '

' + toTime(trip.time) + '

';
			}

		}
		$('#stop-trips').html(content);

		if (settings.panMap) map.panTo({
			lat: stop.lat,
			lng: stop.lng
		});

		if (!settings.fadeIn) return;

		document.title = 'Bussiaeg - ' + stop.name + (stop.desc ? ' - ' + stop.desc : '');
		history.pushState(null, document.title, '/?stop=' + stop.id);

		$('#stop-name').text(stop.name);
		$('#stop-desc').text(stop.desc);

		$('#stop').fadeIn(fadeTime);
	});

}

function showStops() {

	if (map.getZoom() <= 15) return map.eachLayer(function(layer) {
		if (layer._icon !== undefined) map.removeLayer(layer);
	});

	var bounds = map.getBounds();
	$.get('//' + location.host + '/getstops?lat_min=' + bounds._northEast.lat + '&lng_min=' + bounds._northEast.lng + '&lat_max=' + bounds._southWest.lat + '&lng_max=' + bounds._southWest.lng, function(stops) {

		for (var i = 0; i < stops.length; i++) {

			(function() {
				var stop = stops[i];

				L.marker([stop.lat, stop.lng], {
					icon: L.icon({
						iconUrl: 'assets/stop.png',
						iconSize: [24, 24],
						iconAnchor: [0, 0],
					})
				}).addTo(map).on('click', function() {
					if (updater) return;

					showStop(stop.id, {panMap: false, fadeIn: true});
					updater = setInterval(function() {
						showStop(stop.id, {panMap: false, fadeIn: false});
					}, updateTime);

				});

			})();

		}

	});

}

// Functions (Bookmarks)

function addBookmark() {

	swal({
		type: 'input',
		title: 'Sisesta nimi',
		confirmButtonColor: 'deepskyblue',
		confirmButtonText: 'Lisa',
		showCancelButton: true,
		cancelButtonColor: '#fa5858',
		cancelButtonText: 'Tagasi',
		inputPlaceholder: 'Minu kodu',
		animation: 'slide-from-top'
	}, function(input) {
		var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

		if (input !== false && $.trim(input) != '') bookmarks.push({
			name: input,
			lat:  map.getCenter().lat,
			lng:  map.getCenter().lng,
			zoom: map.getZoom()
		});

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));

		hideBookmarks();
	});

}

function deleteBookmark(el) {

	swal({
		title: 'Kustuta?',
		text: el.text(),
		confirmButtonText: 'Jah',
		cancelButtonText: 'Ei',
		showCancelButton: true
	}, function(isConfirm) {
		if (!isConfirm) return;

		var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));

		for (var i = 0; i < bookmarks.length; i++) {
			var bookmark = bookmarks[i];

			if (bookmark.lat != el.data('lat') || bookmark.lng != el.data('lng')) continue;

			bookmarks.splice(i, 1);
			el.remove();

			break;
		}

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
	});

}

function showBookmarks() {
	var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

	var content = '
Lisa asukoht
';
	for (var i = 0; i < bookmarks.length; i++) {
		var bookmark = bookmarks[i];

		content += '
' + bookmark.name + '
';

	}
	$('#bookmarks').html(content);

	$('.bookmark').each(function() {
		$(this).hammer().on('swiperight', function() {
			deleteBookmark($(this));
		});
	});

	$('#bookmarks').animate({left: '0px'}, fadeTime);
}

function hideBookmarks() {
	$('#bookmarks').animate({left: '-240px'}, fadeTime);
}

// Initialization

const share = parseInt(getParameter('stop'));
if (share) if (!isNaN(share)) if (!updater) {

	showStop(share, {panMap: true, fadeIn: true});
	updater = setInterval(function() {
		showStop(share, {panMap: false, fadeIn: false});
	}, updateTime);

}

var map = L.map('map', {
	center: [
		parseFloat(getParameter('lat')) || 59.388,
		parseFloat(getParameter('lng')) || 24.685
	],
	zoom: 16,
	minZoom: 10,
	maxZoom: 18,
	maxBounds: [[59.874204, 21.396935], [57.290822, 28.838625]],
	attributionControl: false,
	bounceAtZoomLimits: false,
	doubleClickZoom: false,
	zoomControl: false,
	keyboard: false,
	boxZoom: false
});

L.tileLayer('//{s}.google.com/vt/lyrs=phl=en&x={x}&y={y}&z={z}&pbhttps://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i372053562!3m14!2sen-US!3sUS!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2zcy50OjY2fHAudjpvZmY!4e0', {
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);

L.control.attribution({
	position: 'bottomleft',
	prefix: 'Bussiaeg.ee | Sõiduplaanid | Peatus | Google | Leaflet'
}).addTo(map);

showStops();
map.on('moveend', function() {
	showStops();
});

navigator.geolocation.getCurrentPosition(function(pos) {
	if (pos.coords.accuracy < 250) map.panTo([pos.coords.latitude, pos.coords.longitude]);
});

navigator.geolocation.watchPosition(function(pos) {
	coords = pos.coords;
});

// User Interface (Stops)

$('#stop-top').click(function() {
	clearInterval(updater); updater = null;

	$(this).parent().fadeOut(fadeTime);

	document.title = 'Bussiaeg';
	history.pushState(null, document.title, '/');

});

// User Interface (Bookmarks)

map.on('click dragstart', function() {
	$(this).addClass('bounce');
	hideBookmarks();
});

$('#bookmarks').hammer().on('swipeleft', function() {
	hideBookmarks();
});

$('#btn-bookmarks').click(function() {
	$(this).addClass('bounce');
	showBookmarks();
});

$('#bookmarks').on('click', '#bookmarks-add', function() {
	addBookmark();
});

$('#bookmarks').on('click', '.bookmark', function() {
	hideBookmarks();

	map.panTo([$(this).data('lat'), $(this).data('lng')]);
	map.setZoom($(this).data('zoom'));

});

// User Interface (Help)

$('#btn-help').click(function() {
	$(this).addClass('bounce');

	hideBookmarks();

	$('#help').fadeIn(fadeTime, function() {
		$('#btn-help').removeClass('bounce');
	});

});

$('#help').click(function() {
	$(this).fadeOut(fadeTime);
});

// User Interface (Locate)

$('#btn-locate').click(function() {
	$(this).addClass('bounce');
	if (coords) map.panTo([coords.latitude, coords.longitude]);
});

$('#btn-locate').on('animationend', function() {
	$(this).removeClass('bounce');
});<|endoftext|>


<commit_before>import PropTypes from "prop-types";
import React from "react";
import styled from "styled-components";

const Counter = ({ value, ...rest }) =>
  !isNaN(parseInt(value)) && value > 0 ? (
    <svg
      {...rest}
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx="8" cy="8" r="8" fill="inherit" />
      <text
        x="8"
        y="8"
        dominantBaseline="central"
        textAnchor="middle"
        fontSize={String(value).length > 1 ? "8px" : "10px"}
        fontWeight="700"
        fill="#FFFFFF"
      >
        {String(value).length > 2 ? "+99" : value}
      </text>
    </svg>
  ) : null;

Counter.propTypes = {
  value: PropTypes.number,
};

const CounterBadge = styled(Counter)`
  fill: ${(props) => props.theme.redNSP};
  z-index: ${(props) => props.theme.zindexNavigationCounter};

  text {
    fill: ${(props) => props.theme.white};
  }
`;
export default CounterBadge;
<commit_msg>fix: CounterBadge component rendering condition<commit_after>import PropTypes from "prop-types";
import React from "react";
import styled from "styled-components";

const Counter = ({ value, ...rest }) =>
  !isNaN(value) && parseInt(value) > 0 ? (
    <svg
      {...rest}
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx="8" cy="8" r="8" fill="inherit" />
      <text
        x="8"
        y="8"
        dominantBaseline="central"
        textAnchor="middle"
        fontSize={String(value).length > 1 ? "8px" : "10px"}
        fontWeight="700"
        fill="#FFFFFF"
      >
        {String(value).length > 2 ? "+99" : value}
      </text>
    </svg>
  ) : null;

Counter.propTypes = {
  value: PropTypes.number,
};

const CounterBadge = styled(Counter)`
  fill: ${(props) => props.theme.redNSP};
  z-index: ${(props) => props.theme.zindexNavigationCounter};

  text {
    fill: ${(props) => props.theme.white};
  }
`;
export default CounterBadge;
<|endoftext|>


<commit_before>/**
 * Quick Create: RelateCreateWindow; simple form in a popup window to create a new record
 */
Ext.ns('Garp');

Garp.RelateCreateWindow = Ext.extend(Ext.Window,{

	model: '',
	iconCls: '',
	title: '',
	cls: 'relate-create-window',
	modal: true,
	width: 640,
	border: true,
	preventBodyReset: false,
	autoScroll: true,

	rec: null,

	quickCreatableInit: Ext.emptyFn,

	initComponent: function(){

		if (!this.iconCls) {
			this.setIconClass(Garp.dataTypes[this.model].iconCls);
		}
		if (!this.title) {
			this.setTitle(Garp.dataTypes[this.model].text);
		}
		this.addEvents('aftersave', 'afterinit');

		this.writer = new Ext.data.JsonWriter({
			paramsAsHash: false,
			encode: false
		});
		var fields = [];
		var cm = Garp.dataTypes[this.model].columnModel;
		Ext.each(cm, function(c){
			fields.push(c.dataIndex);
		});

		this.store = new Ext.data.DirectStore({
			fields: fields,
			autoLoad: false,
			autoSave: false,
			pruneModifiedRecords: true,
			remoteSort: true,
			restful: true,
			autoDestroy: true,
			root: 'rows',
			idProperty: 'id',
			sortInfo: Garp.dataTypes[this.model].sortInfo || null,
			baseParams: {
				start: 0,
				limit: Garp.pageSize
			},
			api: {
				create: Garp[this.model].create || Ext.emptyFn,
				read: Garp[this.model].fetch || Ext.emptyFn,
				update: Garp[this.model].update || Ext.emptyFn,
				destroy: Garp[this.model].destroy || Ext.emptyFn
			},
			writer: this.writer
		});

		//var items = Ext.decode(Ext.encode(Ext.apply({}, Garp.dataTypes[this.model]).formConfig[0])); // cheap deep copy //@TODO: improve
		var items = Ext.apply({}, Garp.dataTypes[this.model].formConfig[0].items[0]); // better copy
		var listeners = Ext.apply({}, Garp.dataTypes[this.model].formConfig[0].listeners);
		items = {
			ref: '../formcontent',
			items: [items],
			listeners: listeners,
			bodyCssClass: 'garp-formpanel',
			border: false
		};

		// Now hide disabled items, they have no function when adding a new item. It may otherwise confuse users:
		// Also: if the field is not in the columnModel, it has no place here in this window
		this.items = [{
			border: false,
			xtype: 'form',
			layout: 'form',
			ref: 'form',
			defaults: {
				autoWidth: true,
				border: false,
				bodyCssClass: 'garp-formpanel' // visual styling
			},
			items: items
		}];

		if (!this.buttons) {
			this.buttons = [];
		}
		this.buttons.push([{
			text: __('Cancel'),
			ref: '../cancelBtn',
			handler: function(){
				this.close();
			},
			scope: this
		}, {
			text: __('Ok'),
			ref: '../okBtn',
			handler: function(){
				this.form.getForm().updateRecord(this.rec);
				if (this.store.getModifiedRecords().length) {
					this.store.on({
						'save': {
							fn: function(){
								this.fireEvent('aftersave', this, this.rec);
								this.close();
							},
							single: true,
							scope: this
						}
					});
					this.saveAll();
				} else {
					this.fireEvent('aftersave', this, this.rec);
					this.close();
				}
			},
			scope: this
		}]);

		Garp.RelateCreateWindow.superclass.initComponent.call(this);
	},

	saveAll: function(){

		this.rec.reject();
		this.rec.beginEdit();
		this.form.getForm().updateRecord(this.rec);
		this.rec.endEdit();
		this.store.on({
			'save': {
				fn: function(){
					this.rec = this.store.getAt(0);

					this.formcontent.fireEvent('loaddata', this.rec, this);
					this.fireEvent('aftersave', this, this.rec);
					this.loadMask.hide();
					//this.close();
				},
				single: true,
				scope: this
			}
		});

		this.loadMask = new Ext.LoadMask(this.getEl(), {
			store: this.store
		});

		if (this.store.getModifiedRecords().length) {
			this.loadMask.show();
			this.store.save();
		}
	},

	afterRender: function(){
		Garp.RelateCreateWindow.superclass.afterRender.call(this);
		this.form.getForm().remove(this.form.getForm().findField('id'));
		this.form.getForm().setValues(Garp.dataTypes[this.model].defaultData);
		if (this.onShow) {
			this.onShow.call(this);
		}
		var rec = new this.store.recordType(Ext.apply({}, Garp.dataTypes[this.model].defaultData));
		this.rec = rec;
		this.store.insert(0, rec);

		this.getForm = function(){
			return this.form.getForm();
		};

		this.on('save-all', this.saveAll, this);

		this.on('show', function(){
			this.formcontent.fireEvent('loaddata', rec, this);
			this.quickCreatableInit();
			this.getForm().clearInvalid();
			if (this.quickCreateReference) {
				var id = this.parentId || Garp.gridPanel.getSelectionModel().getSelected().get('id');
				this.getForm().findField(this.quickCreateReference).store.on('load', function(){
					this.getForm().findField(this.quickCreateReference).setValue(id);
				}, this, {
					single: true
				});
				this.getForm().findField(this.quickCreateReference).store.load();
			}
			this.keymap = new Ext.KeyMap(this.formcontent.getEl(), [{
				key: Ext.EventObject.ENTER,
				ctrl: true,
				scope: this,
				handler: function(e){
					this.form.getForm().items.each(function(){
						this.fireEvent('blur', this);
					});
					this.okBtn.handler.call(this);
					return false;
				}
			}]);
			this.keymap.stopEvent = true; // prevents browser key handling.
			this.fireEvent('afterinit', this);
		}, this);
	}
});<commit_msg>Ticket #880 JS error bij toevoegen video<commit_after>/**
 * Quick Create: RelateCreateWindow; simple form in a popup window to create a new record
 */
Ext.ns('Garp');

Garp.RelateCreateWindow = Ext.extend(Ext.Window,{

	model: '',
	iconCls: '',
	title: '',
	cls: 'relate-create-window',
	modal: true,
	width: 640,
	border: true,
	preventBodyReset: false,
	autoScroll: true,

	rec: null,

	quickCreatableInit: Ext.emptyFn,

	initComponent: function(){

		if (!this.iconCls) {
			this.setIconClass(Garp.dataTypes[this.model].iconCls);
		}
		if (!this.title) {
			this.setTitle(Garp.dataTypes[this.model].text);
		}
		this.addEvents('aftersave', 'afterinit');

		this.writer = new Ext.data.JsonWriter({
			paramsAsHash: false,
			encode: false
		});
		var fields = [];
		var cm = Garp.dataTypes[this.model].columnModel;
		Ext.each(cm, function(c){
			fields.push(c.dataIndex);
		});

		this.store = new Ext.data.DirectStore({
			fields: fields,
			autoLoad: false,
			autoSave: false,
			pruneModifiedRecords: true,
			remoteSort: true,
			restful: true,
			autoDestroy: true,
			root: 'rows',
			idProperty: 'id',
			sortInfo: Garp.dataTypes[this.model].sortInfo || null,
			baseParams: {
				start: 0,
				limit: Garp.pageSize
			},
			api: {
				create: Garp[this.model].create || Ext.emptyFn,
				read: Garp[this.model].fetch || Ext.emptyFn,
				update: Garp[this.model].update || Ext.emptyFn,
				destroy: Garp[this.model].destroy || Ext.emptyFn
			},
			writer: this.writer
		});

		//var items = Ext.decode(Ext.encode(Ext.apply({}, Garp.dataTypes[this.model]).formConfig[0])); // cheap deep copy //@TODO: improve
		var items = Ext.apply({}, Garp.dataTypes[this.model].formConfig[0].items[0]); // better copy
		var listeners = Ext.apply({}, Garp.dataTypes[this.model].formConfig[0].listeners);
		items = {
			ref: '../formcontent',
			items: [items],
			listeners: listeners,
			bodyCssClass: 'garp-formpanel',
			border: false
		};

		// Now hide disabled items, they have no function when adding a new item. It may otherwise confuse users:
		// Also: if the field is not in the columnModel, it has no place here in this window
		this.items = [{
			border: false,
			xtype: 'form',
			layout: 'form',
			ref: 'form',
			defaults: {
				autoWidth: true,
				border: false,
				bodyCssClass: 'garp-formpanel' // visual styling
			},
			items: items
		}];

		if (!this.buttons) {
			this.buttons = [];
		}
		this.buttons.push([{
			text: __('Cancel'),
			ref: '../cancelBtn',
			handler: function(){
				this.close();
			},
			scope: this
		}, {
			text: __('Ok'),
			ref: '../okBtn',
			handler: function(){
				this.form.getForm().updateRecord(this.rec);
				if (this.store.getModifiedRecords().length) {
					this.store.on({
						'save': {
							fn: function(){
								this.fireEvent('aftersave', this, this.rec);
								this.close();
							},
							single: true,
							scope: this
						}
					});
					this.saveAll();
				} else {
					this.fireEvent('aftersave', this, this.rec);
					this.close();
				}
			},
			scope: this
		}]);

		Garp.RelateCreateWindow.superclass.initComponent.call(this);
	},

	saveAll: function(){

		this.rec.reject();
		this.rec.beginEdit();
		this.form.getForm().updateRecord(this.rec);
		this.rec.endEdit();
		this.store.on({
			'save': {
				fn: function(){
					this.rec = this.store.getAt(0);
					if (this.formcontent) {
						this.formcontent.fireEvent('loaddata', this.rec, this);
					}
					this.fireEvent('aftersave', this, this.rec);
					this.loadMask.hide();
					//this.close();
				},
				single: true,
				scope: this
			}
		});

		this.loadMask = new Ext.LoadMask(this.getEl(), {
			store: this.store
		});

		if (this.store.getModifiedRecords().length) {
			this.loadMask.show();
			this.store.save();
		}
	},

	afterRender: function(){
		Garp.RelateCreateWindow.superclass.afterRender.call(this);
		this.form.getForm().remove(this.form.getForm().findField('id'));
		this.form.getForm().setValues(Garp.dataTypes[this.model].defaultData);
		if (this.onShow) {
			this.onShow.call(this);
		}
		var rec = new this.store.recordType(Ext.apply({}, Garp.dataTypes[this.model].defaultData));
		this.rec = rec;
		this.store.insert(0, rec);

		this.getForm = function(){
			return this.form.getForm();
		};

		this.on('save-all', this.saveAll, this);

		this.on('show', function(){
			this.formcontent.fireEvent('loaddata', rec, this);
			this.quickCreatableInit();
			this.getForm().clearInvalid();
			if (this.quickCreateReference) {
				var id = this.parentId || Garp.gridPanel.getSelectionModel().getSelected().get('id');
				this.getForm().findField(this.quickCreateReference).store.on('load', function(){
					this.getForm().findField(this.quickCreateReference).setValue(id);
				}, this, {
					single: true
				});
				this.getForm().findField(this.quickCreateReference).store.load();
			}
			this.keymap = new Ext.KeyMap(this.formcontent.getEl(), [{
				key: Ext.EventObject.ENTER,
				ctrl: true,
				scope: this,
				handler: function(e){
					this.form.getForm().items.each(function(){
						this.fireEvent('blur', this);
					});
					this.okBtn.handler.call(this);
					return false;
				}
			}]);
			this.keymap.stopEvent = true; // prevents browser key handling.
			this.fireEvent('afterinit', this);
		}, this);
	}
});<|endoftext|>


<commit_before>// Declarations

var coords, map, updater, live;

const fadeTime = 250,
	  updateTime = 2000;

// Functions (Stops)

function getParameter(name) {
	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}

function getSecondsSinceMidnight() {
	return ~~((new Date() - new Date().setHours(0, 0, 0, 0)) / 1000);
}

function toHMS(seconds) {
	var hours   = Math.floor(seconds / 3600),
		minutes = Math.floor((seconds % 3600) / 60);
	seconds = seconds - (minutes * 60) - (hours * 3600);
	return {
		h: ('0' + hours).slice(-2),
		m: ('0' + minutes).slice(-2),
		s: ('0' + seconds).slice(-2)
	};
}

function toCountdown(seconds, showHours) {
	seconds = seconds - getSecondsSinceMidnight();
	var time = toHMS(Math.abs(seconds));
	return (seconds < 0 ? '-' : '') + (showHours ? time.h + 'h ': '') + time.m + 'm ' + (showHours ? '' : time.s + 's');
}

function toTime(seconds) {
	var time = toHMS(seconds);
	return time.h + ':' + time.m;
}

function showStop(id, settings) {
	if (updater && !live) {

		$('.trip-scheduled').each(function() {
			$(this).text(toCountdown($(this).attr('data-time'), true));

			if ($(this).attr('data-time') > getSecondsSinceMidnight()) return;

			$(this).parent().animate({left: window.innerWidth + 'px'}, 1000, function() {
				$(this).remove();

				$('.trip').each(function() {
					$(this).animate({top: (parseInt($(this).css('top')) - 80) + 'px'}, 500);
				});

			});

		});

		return;
	}

	$.get('//' + location.host + '/getstop?id=' + id).done(function(data) {
		var trips   = data.trips,
			content = '';

		live = data.live;

		for (var i = 0; i < trips.length; i++) {
			var trip = trips[i];

			if (live) {
				content += '

' + trip.number + '

' + toCountdown(trip.scheduled, false) + '

' + toCountdown(trip.expected, false) + '

';
			} else {
				content += '

' + trip.number + '

' + toCountdown(trip.time, true) + '

' + toTime(trip.time) + '

';
			}

		}
		$('#stop-trips').html(content);

		if (settings.panMap) map.panTo({
			lat: data.lat,
			lng: data.lng
		});

		if (!settings.fadeIn) return;

		document.title = 'Bussiaeg - ' + data.name + (data.desc ? ' - ' + data.desc : '');
		history.pushState(null, document.title, '/?stop=' + data.id);

		$('#stop-name').text(data.name);
		$('#stop-desc').text(data.desc);

		$('#stop').fadeIn(fadeTime);
	});

}

function showStops() {

	if (map.getZoom() <= 15) return map.eachLayer(function(layer) {
		if (layer._icon !== undefined) map.removeLayer(layer);
	});

	var bounds = map.getBounds();
	$.get('//' + location.host + '/getstops?lat_min=' + bounds._northEast.lat + '&lng_min=' + bounds._northEast.lng + '&lat_max=' + bounds._southWest.lat + '&lng_max=' + bounds._southWest.lng, function(stops) {

		for (var i = 0; i < stops.length; i++) {

			(function() {
				var stop = stops[i];

				L.marker([stop.lat, stop.lng], {
					icon: L.icon({
						iconUrl: 'assets/stop.png',
						iconSize: [24, 24],
						iconAnchor: [0, 0],
					})
				}).addTo(map).on('click', function() {
					if (updater) return;

					showStop(stop.id, {panMap: false, fadeIn: true});
					updater = setInterval(function() {
						showStop(stop.id, {panMap: false, fadeIn: false});
					}, updateTime);

				});

			})();

		}

	});

}

// Functions (Bookmarks)

function addBookmark() {

	swal({
		type: 'input',
		title: 'Sisesta nimi',
		confirmButtonColor: 'deepskyblue',
		confirmButtonText: 'Lisa',
		showCancelButton: true,
		cancelButtonColor: '#fa5858',
		cancelButtonText: 'Tagasi',
		inputPlaceholder: 'Minu kodu',
		animation: 'slide-from-top'
	}, function(input) {
		var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

		if (input !== false && $.trim(input) != '') bookmarks.push({
			name: input,
			lat:  map.getCenter().lat,
			lng:  map.getCenter().lng,
			zoom: map.getZoom()
		});

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));

		hideBookmarks();
	});

}

function deleteBookmark(el) {

	swal({
		title: 'Kustuta?',
		text: el.text(),
		confirmButtonText: 'Jah',
		cancelButtonText: 'Ei',
		showCancelButton: true
	}, function(isConfirm) {
		if (!isConfirm) return;

		var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));

		for (var i = 0; i < bookmarks.length; i++) {
			var bookmark = bookmarks[i];

			if (bookmark.lat != el.data('lat') || bookmark.lng != el.data('lng')) continue;

			bookmarks.splice(i, 1);
			el.remove();

			break;
		}

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
	});

}

function showBookmarks() {
	var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

	var content = '
Lisa asukoht
';
	for (var i = 0; i < bookmarks.length; i++) {
		var bookmark = bookmarks[i];

		content += '
' + bookmark.name + '
';

	}
	$('#bookmarks').html(content);

	$('.bookmark').each(function() {
		$(this).hammer().on('swipeleft', function() {
			deleteBookmark($(this));
		});
	});

	$('#bookmarks').animate({left: '0px'}, fadeTime);
}

function hideBookmarks() {
	$('#bookmarks').animate({left: '-240px'}, fadeTime);
}

// Initialization

const share = parseInt(getParameter('stop'));
if (share) if (!isNaN(share)) if (!updater) {

	showStop(share, {panMap: true, fadeIn: true});
	updater = setInterval(function() {
		showStop(share, {panMap: false, fadeIn: false});
	}, updateTime);

}

var map = L.map('map', {
	center: [
		parseFloat(getParameter('lat')) || 59.388,
		parseFloat(getParameter('lng')) || 24.685
	],
	zoom: 16,
	minZoom: 10,
	maxZoom: 18,
	maxBounds: [[59.874204, 21.396935], [57.290822, 28.838625]],
	attributionControl: false,
	bounceAtZoomLimits: false,
	doubleClickZoom: false,
	zoomControl: false,
	keyboard: false,
	boxZoom: false
});

L.tileLayer('//{s}.google.com/vt/lyrs=phl=en&x={x}&y={y}&z={z}&pbhttps://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i372053562!3m14!2sen-US!3sUS!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2zcy50OjY2fHAudjpvZmY!4e0', {
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);

L.control.attribution({
	position: 'bottomleft',
	prefix: 'Bussiaeg.ee | Sõiduplaanid | Peatus | Google | Leaflet'
}).addTo(map);

showStops();
map.on('moveend', function() {
	showStops();
});

navigator.geolocation.getCurrentPosition(function(pos) {
	if (pos.coords.accuracy < 250) map.panTo([pos.coords.latitude, pos.coords.longitude]);
});

navigator.geolocation.watchPosition(function(pos) {
	coords = pos.coords;
});

// User Interface (Stops)

$('#stop-top').click(function() {
	clearInterval(updater); updater = null;

	$(this).parent().fadeOut(fadeTime, function() {
		$('#stop-name').text('...');
		$('#stop-desc').text('...');
		$('#stop-trips').html('');
	});

});

// User Interface (Bookmarks)

map.on('click', function() {
	$(this).addClass('bounce');
	hideBookmarks();
});

$('#bookmarks').hammer().on('swipeleft', function() {
	hideBookmarks();
});

$('#btn-bookmarks').click(function() {
	$(this).addClass('bounce');
	showBookmarks();
});

$('#bookmarks').on('click', '#bookmarks-add', function() {
	addBookmark();
});

$('#bookmarks').on('click', '.bookmark', function() {
	hideBookmarks();

	map.panTo([$(this).data('lat'), $(this).data('lng')]);
	map.setZoom($(this).data('zoom'));

});

// User Interface (Help)

$('#btn-help').click(function() {
	$(this).addClass('bounce');

	hideBookmarks();

	$('#help').fadeIn(fadeTime, function() {
		$('#btn-help').removeClass('bounce');
	});

});

$('#help').click(function() {
	$(this).fadeOut(fadeTime);
});

// User Interface (Locate)

$('#btn-locate').click(function() {
	$(this).addClass('bounce');
	if (coords) map.panTo([coords.latitude, coords.longitude]);
});

$('#btn-locate').on('animationend', function() {
	$(this).removeClass('bounce');
});<commit_msg>Change swipe direction for deleting bookmarks<commit_after>// Declarations

var coords, map, updater, live;

const fadeTime = 250,
	  updateTime = 2000;

// Functions (Stops)

function getParameter(name) {
	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}

function getSecondsSinceMidnight() {
	return ~~((new Date() - new Date().setHours(0, 0, 0, 0)) / 1000);
}

function toHMS(seconds) {
	var hours   = Math.floor(seconds / 3600),
		minutes = Math.floor((seconds % 3600) / 60);
	seconds = seconds - (minutes * 60) - (hours * 3600);
	return {
		h: ('0' + hours).slice(-2),
		m: ('0' + minutes).slice(-2),
		s: ('0' + seconds).slice(-2)
	};
}

function toCountdown(seconds, showHours) {
	seconds = seconds - getSecondsSinceMidnight();
	var time = toHMS(Math.abs(seconds));
	return (seconds < 0 ? '-' : '') + (showHours ? time.h + 'h ': '') + time.m + 'm ' + (showHours ? '' : time.s + 's');
}

function toTime(seconds) {
	var time = toHMS(seconds);
	return time.h + ':' + time.m;
}

function showStop(id, settings) {
	if (updater && !live) {

		$('.trip-scheduled').each(function() {
			$(this).text(toCountdown($(this).attr('data-time'), true));

			if ($(this).attr('data-time') > getSecondsSinceMidnight()) return;

			$(this).parent().animate({left: window.innerWidth + 'px'}, 1000, function() {
				$(this).remove();

				$('.trip').each(function() {
					$(this).animate({top: (parseInt($(this).css('top')) - 80) + 'px'}, 500);
				});

			});

		});

		return;
	}

	$.get('//' + location.host + '/getstop?id=' + id).done(function(data) {
		var trips   = data.trips,
			content = '';

		live = data.live;

		for (var i = 0; i < trips.length; i++) {
			var trip = trips[i];

			if (live) {
				content += '

' + trip.number + '

' + toCountdown(trip.scheduled, false) + '

' + toCountdown(trip.expected, false) + '

';
			} else {
				content += '

' + trip.number + '

' + toCountdown(trip.time, true) + '

' + toTime(trip.time) + '

';
			}

		}
		$('#stop-trips').html(content);

		if (settings.panMap) map.panTo({
			lat: data.lat,
			lng: data.lng
		});

		if (!settings.fadeIn) return;

		document.title = 'Bussiaeg - ' + data.name + (data.desc ? ' - ' + data.desc : '');
		history.pushState(null, document.title, '/?stop=' + data.id);

		$('#stop-name').text(data.name);
		$('#stop-desc').text(data.desc);

		$('#stop').fadeIn(fadeTime);
	});

}

function showStops() {

	if (map.getZoom() <= 15) return map.eachLayer(function(layer) {
		if (layer._icon !== undefined) map.removeLayer(layer);
	});

	var bounds = map.getBounds();
	$.get('//' + location.host + '/getstops?lat_min=' + bounds._northEast.lat + '&lng_min=' + bounds._northEast.lng + '&lat_max=' + bounds._southWest.lat + '&lng_max=' + bounds._southWest.lng, function(stops) {

		for (var i = 0; i < stops.length; i++) {

			(function() {
				var stop = stops[i];

				L.marker([stop.lat, stop.lng], {
					icon: L.icon({
						iconUrl: 'assets/stop.png',
						iconSize: [24, 24],
						iconAnchor: [0, 0],
					})
				}).addTo(map).on('click', function() {
					if (updater) return;

					showStop(stop.id, {panMap: false, fadeIn: true});
					updater = setInterval(function() {
						showStop(stop.id, {panMap: false, fadeIn: false});
					}, updateTime);

				});

			})();

		}

	});

}

// Functions (Bookmarks)

function addBookmark() {

	swal({
		type: 'input',
		title: 'Sisesta nimi',
		confirmButtonColor: 'deepskyblue',
		confirmButtonText: 'Lisa',
		showCancelButton: true,
		cancelButtonColor: '#fa5858',
		cancelButtonText: 'Tagasi',
		inputPlaceholder: 'Minu kodu',
		animation: 'slide-from-top'
	}, function(input) {
		var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

		if (input !== false && $.trim(input) != '') bookmarks.push({
			name: input,
			lat:  map.getCenter().lat,
			lng:  map.getCenter().lng,
			zoom: map.getZoom()
		});

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));

		hideBookmarks();
	});

}

function deleteBookmark(el) {

	swal({
		title: 'Kustuta?',
		text: el.text(),
		confirmButtonText: 'Jah',
		cancelButtonText: 'Ei',
		showCancelButton: true
	}, function(isConfirm) {
		if (!isConfirm) return;

		var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));

		for (var i = 0; i < bookmarks.length; i++) {
			var bookmark = bookmarks[i];

			if (bookmark.lat != el.data('lat') || bookmark.lng != el.data('lng')) continue;

			bookmarks.splice(i, 1);
			el.remove();

			break;
		}

		localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
	});

}

function showBookmarks() {
	var bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || [];

	var content = '
Lisa asukoht
';
	for (var i = 0; i < bookmarks.length; i++) {
		var bookmark = bookmarks[i];

		content += '
' + bookmark.name + '
';

	}
	$('#bookmarks').html(content);

	$('.bookmark').each(function() {
		$(this).hammer().on('swiperight', function() {
			deleteBookmark($(this));
		});
	});

	$('#bookmarks').animate({left: '0px'}, fadeTime);
}

function hideBookmarks() {
	$('#bookmarks').animate({left: '-240px'}, fadeTime);
}

// Initialization

const share = parseInt(getParameter('stop'));
if (share) if (!isNaN(share)) if (!updater) {

	showStop(share, {panMap: true, fadeIn: true});
	updater = setInterval(function() {
		showStop(share, {panMap: false, fadeIn: false});
	}, updateTime);

}

var map = L.map('map', {
	center: [
		parseFloat(getParameter('lat')) || 59.388,
		parseFloat(getParameter('lng')) || 24.685
	],
	zoom: 16,
	minZoom: 10,
	maxZoom: 18,
	maxBounds: [[59.874204, 21.396935], [57.290822, 28.838625]],
	attributionControl: false,
	bounceAtZoomLimits: false,
	doubleClickZoom: false,
	zoomControl: false,
	keyboard: false,
	boxZoom: false
});

L.tileLayer('//{s}.google.com/vt/lyrs=phl=en&x={x}&y={y}&z={z}&pbhttps://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i372053562!3m14!2sen-US!3sUS!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2zcy50OjY2fHAudjpvZmY!4e0', {
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
}).addTo(map);

L.control.attribution({
	position: 'bottomleft',
	prefix: 'Bussiaeg.ee | Sõiduplaanid | Peatus | Google | Leaflet'
}).addTo(map);

showStops();
map.on('moveend', function() {
	showStops();
});

navigator.geolocation.getCurrentPosition(function(pos) {
	if (pos.coords.accuracy < 250) map.panTo([pos.coords.latitude, pos.coords.longitude]);
});

navigator.geolocation.watchPosition(function(pos) {
	coords = pos.coords;
});

// User Interface (Stops)

$('#stop-top').click(function() {
	clearInterval(updater); updater = null;

	$(this).parent().fadeOut(fadeTime, function() {
		$('#stop-name').text('...');
		$('#stop-desc').text('...');
		$('#stop-trips').html('');
	});

});

// User Interface (Bookmarks)

map.on('click', function() {
	$(this).addClass('bounce');
	hideBookmarks();
});

$('#bookmarks').hammer().on('swipeleft', function() {
	hideBookmarks();
});

$('#btn-bookmarks').click(function() {
	$(this).addClass('bounce');
	showBookmarks();
});

$('#bookmarks').on('click', '#bookmarks-add', function() {
	addBookmark();
});

$('#bookmarks').on('click', '.bookmark', function() {
	hideBookmarks();

	map.panTo([$(this).data('lat'), $(this).data('lng')]);
	map.setZoom($(this).data('zoom'));

});

// User Interface (Help)

$('#btn-help').click(function() {
	$(this).addClass('bounce');

	hideBookmarks();

	$('#help').fadeIn(fadeTime, function() {
		$('#btn-help').removeClass('bounce');
	});

});

$('#help').click(function() {
	$(this).fadeOut(fadeTime);
});

// User Interface (Locate)

$('#btn-locate').click(function() {
	$(this).addClass('bounce');
	if (coords) map.panTo([coords.latitude, coords.longitude]);
});

$('#btn-locate').on('animationend', function() {
	$(this).removeClass('bounce');
});<|endoftext|>