mirror of
https://github.com/Ekokumppanit/Bicyclesim.git
synced 2026-02-22 00:49:51 +00:00
Initial commit.
This commit is contained in:
197
client/bicyclesim-meteor.html
Normal file
197
client/bicyclesim-meteor.html
Normal file
@@ -0,0 +1,197 @@
|
||||
<head>
|
||||
<title>Polkupyöräsimulaattori</title>
|
||||
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap.no-icons.min.css" rel="stylesheet">
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/2.0/css/font-awesome.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="street" class="street"></div>
|
||||
<div id="map" class="map"></div>
|
||||
{{> main}}
|
||||
</body>
|
||||
|
||||
<template name="main">
|
||||
{{#if editing}}
|
||||
{{> editing}}
|
||||
{{/if}}
|
||||
{{#if sim}}
|
||||
{{> sim}}
|
||||
{{/if}}
|
||||
{{#if frontpage}}
|
||||
{{> frontpage}}
|
||||
{{/if}}
|
||||
{{#if frontpage_edit}}
|
||||
{{> frontpage}}
|
||||
{{/if}}
|
||||
{{#if help}}
|
||||
{{> help}}
|
||||
{{/if}}
|
||||
{{#if settings}}
|
||||
{{> settings}}
|
||||
{{/if}}
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-bottom">
|
||||
<div class="navbar-inner">
|
||||
<a class="brand" href="/">Bicycle sim <span>v0.1</span></a>
|
||||
<ul class="nav">
|
||||
<li{{#if frontpage}} class="active"{{/if}}{{#if sim}} class="active"{{/if}}>
|
||||
<a href="/">
|
||||
<i class="icon-road"></i> Simulaatio
|
||||
{{#if sim}} <span class="divider">/</span> {{sim.name}}{{/if}}
|
||||
</a>
|
||||
</li>
|
||||
<li{{#if frontpage_edit}} class="active"{{/if}}{{#if editing}} class="active"{{/if}}>
|
||||
<a href="/edit">
|
||||
<i class="icon-wrench"></i> Muokkaa
|
||||
{{#if editing}} <span class="divider">/</span> {{editing.name}}{{/if}}
|
||||
</a>
|
||||
</li>
|
||||
<li{{#if settings}} class="active"{{/if}}><a href="/settings"><i class="icon-cogs"></i> Asetukset</a></li>
|
||||
<li{{#if help}} class="active"{{/if}}><a href="/help"><i class="icon-question-sign"></i> Ohjeet</a></li>
|
||||
</ul>
|
||||
<ul class="nav pull-right">
|
||||
<li class="login">{{loginButtons}}</li>
|
||||
<li><a href="http://github.com/Ekokumppanit/Bicyclesim" target="new"><i class="icon-github"></i> Github</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="help">
|
||||
<div class="help">
|
||||
<div class="container">
|
||||
<div class="fluid-row">
|
||||
<h1>Polkupyöräsimulaattori – Kuka, Mitä?</h1>
|
||||
<p>
|
||||
<a href="http://www.ekokumppanit.fi" target="new">Ekokumppanit Oy:n</a> työntekijä Juho Teperi on toteuttanut tämän prototyyppi web-sovelluksen
|
||||
<a href="http://www.expomark.fi/fi/messut/energia2012" target="new">Energia 2012</a> messuja varten.
|
||||
Ohjelmassa voit liikkua (polkea) ympäri maailmaa (tai ainakin valmiita reittejä).
|
||||
</p>
|
||||
|
||||
<h1>Käyttö</h1>
|
||||
<p>
|
||||
Omalla koneellasi voit kokeilla toimintaa valitsemalla <em>Simulaatio</em>-välilehdeltä reitin ja hakkaamalla välilyöntiä.
|
||||
Parhaan kokemuksen saat kun liität tietokoneeseen polkupyörän telineen <small>(traineri tai rolleri)</small> ja nopeusanturin <small>(laite joka lähettää näppäinpainalluksen kun rengas on pyörähtänyt kierroksen)</small> avulla.
|
||||
</p>
|
||||
|
||||
<h1>Toteutus</h1>
|
||||
<p>
|
||||
Ohjelma on toteutettu käyttäen <a href="http://meteor.com" target="new">Meteor</a> JavaSript sovelluskehystä <small>(käyttää palvelinpuolella Node.js)</small>.
|
||||
Kuvat tulevat Google Streetview palvelusta Googlen tarjoaman JavaScript rajapinnan kautta.
|
||||
</p>
|
||||
|
||||
<h1>Vähän siistiä, mistä saan tämän itselleni?</h1>
|
||||
<p>
|
||||
Voit joko käyttää valmista ohjelmaa osoitteessa <a href="http://bicyclesim.ekokumppanit.fi">bicyclesim.ekokumppanit.fi</a> tai
|
||||
hakea ohjelman lähdekoodin <a href="http://github.com/Ekokumppanit/Bicyclesim" target="new">Githubista</a>,
|
||||
voit myös tehdä vapaasti muutoksia koodiin ja lähettää parannuksesi viralliseen versioon jotta muutkin hyötyvät niistä.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="settings">
|
||||
<div class="help">
|
||||
<div class="container">
|
||||
<div class="fluid-row">
|
||||
<form>
|
||||
<legend>Asetukset</legend>
|
||||
<label>Liikekerroin. Vaikuttaa nopeuteen jolla reitillä liikutaan. Ei vaikuta nopeuslukemaan tai kuljettuun matkaan.</label>
|
||||
<input type="text" value="{{settings.multiplier}}" id="settings_multiplier"/>
|
||||
<label>Renkaan koko, tuumia</label>
|
||||
<input type="text" value="{{settings.diameter}}" id="settings_diameter"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="frontpage">
|
||||
<div class="frontpage">
|
||||
<div class="container">
|
||||
<div class="fluid-row">
|
||||
<ul class="thumbnails">
|
||||
{{#each routes}}
|
||||
<li class="span3">
|
||||
{{#if editing}}
|
||||
<div class="thumbnail">
|
||||
<img src="{{staticmap}}" alt=""/>
|
||||
<span>
|
||||
{{#if editing_route_name}}
|
||||
<div class="input"><input type="text" id="route-name" value="{{name}}"/></div>
|
||||
{{else}}
|
||||
{{name}}
|
||||
{{#if can_edit}}<a href="#" class="remove"><i class="icon-remove-sign"></i></a>{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
{{else}}
|
||||
<a href="/{{_id}}" class="thumbnail">
|
||||
<img src="{{staticmap}}" alt=""/>
|
||||
<span>{{name}} <span>{{km route_length}}km</span></span>
|
||||
</a>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#if editing}}
|
||||
<li class="span3 new-route">
|
||||
{{#if logged_in}}<div class="input"><input type="text" id="new-route" placeholder="New route"/></div>{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="fluid-row">
|
||||
<a href="/help" class="btn btn-huge"><i class="icon-question-sign"></i> Ohjeet</a>
|
||||
<a href="http://github.com/Ekokumppanit/Bicyclesim" class="btn btn-huge" target="new"><i class="icon-github"></i> Projekti Githubissa</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="editing">
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-inner">
|
||||
<h3>Owner</h3>
|
||||
<p>{{owner}}</p>
|
||||
{{> points}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="sim">
|
||||
<ul class="speedometer">
|
||||
<li class="distance">{{km distance}} km</li>
|
||||
<li class="speed">{{kmh speed}} <sup>km</sup>/<sub>h<sub></li>
|
||||
<li class="calories"></li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template name="distance">
|
||||
<span>{{distance}}</span> km
|
||||
</template>
|
||||
<template name="speed">
|
||||
<span>{{speed}}</span> <sup>km</sup>/<sub>h</sub>
|
||||
</template>
|
||||
<template name="calories">
|
||||
<span>{{calories}}</span> cal
|
||||
</template>
|
||||
|
||||
<template name="points">
|
||||
<h3>Points
|
||||
<span><label class="checkbox inline"><input type="checkbox" id="points-autoadd" {{#if autoadd}}checked="checked"{{/if}}/>Auto add points</label></span>
|
||||
</h3>
|
||||
<ol class="points">
|
||||
{{#each points}}
|
||||
<li class="point{{#if active}} active{{/if}}">
|
||||
<span class="location">{{lat latlng}}, {{lng latlng}}</span>,
|
||||
<span class="heading">{{round heading 2}}°</span>,
|
||||
<span class="distance">{{round distance 2}}m</span>
|
||||
{{#if can_edit}}<a href="#" class="remove"><i class="icon-remove-sign"></i></a>{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#if can_edit}}<li class="new-point"><button type="button" class="btn"><i class="icon-plus-sign"></i></button></li>{{/if}}
|
||||
</ol>
|
||||
</template>
|
||||
216
client/bicyclesim-meteor.styl
Normal file
216
client/bicyclesim-meteor.styl
Normal file
@@ -0,0 +1,216 @@
|
||||
// --- Variables ---
|
||||
|
||||
$sidebar_width = 300px
|
||||
|
||||
// --- Functions ---
|
||||
|
||||
filter(args ...)
|
||||
-webkit-filter args
|
||||
-moz-filter args
|
||||
filter args
|
||||
|
||||
// ---
|
||||
|
||||
body
|
||||
overflow hidden
|
||||
|
||||
.navbar-inverse .brand span
|
||||
color #FFB0B0
|
||||
font-size 0.8em
|
||||
|
||||
.navbar-fixed-bottom
|
||||
position absolute
|
||||
.navbar-inner
|
||||
padding-left 15px !important
|
||||
padding-right 15px !important
|
||||
i
|
||||
color #fff
|
||||
|
||||
.sidebar
|
||||
li
|
||||
i
|
||||
color #000
|
||||
|
||||
a:hover i
|
||||
text-decoration none
|
||||
.icon-remove, .icon-remove-sign
|
||||
&:hover
|
||||
color #FFAFAF
|
||||
|
||||
|
||||
.btn.btn-huge
|
||||
font-size 34px
|
||||
line-height 34px
|
||||
padding 12px 20px
|
||||
margin-right 30px
|
||||
|
||||
.nav
|
||||
.divider
|
||||
color #666
|
||||
padding 0 10px
|
||||
.login
|
||||
padding-top 6px
|
||||
|
||||
|
||||
div.input
|
||||
padding-right 14px
|
||||
& > input
|
||||
margin-bottom 0
|
||||
width 100%
|
||||
|
||||
.thumbnail
|
||||
background #fff
|
||||
& > span
|
||||
display block
|
||||
background #000
|
||||
background rgba(0, 0, 0, 0.7)
|
||||
color #fff
|
||||
padding 2px 10px
|
||||
line-height 32px
|
||||
font-weight bold
|
||||
& > a
|
||||
float right
|
||||
& > span
|
||||
font-weight normal
|
||||
color #C8C8C8
|
||||
float right
|
||||
&:hover
|
||||
text-decoration none
|
||||
i
|
||||
color #fff
|
||||
|
||||
a.thumbnail
|
||||
&:hover
|
||||
border-color #fff !important
|
||||
box-shadow 0 0 25px #fff !important
|
||||
|
||||
|
||||
.sidebar
|
||||
position absolute
|
||||
width $sidebar_width
|
||||
top 0
|
||||
bottom 42px
|
||||
left 0
|
||||
overflow-y auto
|
||||
|
||||
.sidebar-inner
|
||||
padding 20px
|
||||
|
||||
.street, .map
|
||||
img
|
||||
max-width none
|
||||
border none !important
|
||||
|
||||
.street
|
||||
position absolute
|
||||
left 0
|
||||
right 0
|
||||
bottom 42px
|
||||
top 0
|
||||
z-index 0
|
||||
|
||||
.map
|
||||
position absolute
|
||||
width 300px
|
||||
height 250px
|
||||
left 0
|
||||
top 0
|
||||
opacity 0.8
|
||||
display none
|
||||
|
||||
.street
|
||||
filter(blur(4px))
|
||||
|
||||
.show-map
|
||||
.street
|
||||
filter(blur(0))
|
||||
.map
|
||||
display block
|
||||
|
||||
.show-sidebar
|
||||
.street, .map
|
||||
left $sidebar_width
|
||||
|
||||
.frontpage, .help
|
||||
z-index 100
|
||||
position absolute
|
||||
top 0
|
||||
bottom 42px
|
||||
left 0
|
||||
right 0
|
||||
background rgba(0, 0, 0, 0.5)
|
||||
padding-top 30px
|
||||
overflow-y auto
|
||||
|
||||
.help
|
||||
background #fff
|
||||
font-size 1.5em
|
||||
line-height 1.5em
|
||||
h1
|
||||
margin-top 40px
|
||||
|
||||
.sidebar
|
||||
h3
|
||||
span
|
||||
float right
|
||||
|
||||
ol.points
|
||||
margin 0
|
||||
li
|
||||
font-size 0.9em
|
||||
border-top 1px solid #ccc
|
||||
padding 2px 0px
|
||||
list-style-position inside
|
||||
&:first-child, &.new-point
|
||||
border-top 0
|
||||
&.new-point
|
||||
list-style none
|
||||
span.location
|
||||
font-size 0.8em
|
||||
a.remove
|
||||
float right
|
||||
|
||||
.speedometer
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
background rgba(0, 0, 0, 0.6)
|
||||
width 190px
|
||||
padding 15px
|
||||
border-bottom-left-radius 25px
|
||||
li
|
||||
list-style none
|
||||
text-align right
|
||||
font-size 2.5em
|
||||
color #fff
|
||||
font-weight bold
|
||||
line-height 150%
|
||||
|
||||
::-webkit-scrollbar
|
||||
background transparent
|
||||
border none
|
||||
|
||||
::-webkit-scrollbar:vertical
|
||||
border-width 0 0 0 1px
|
||||
width 11px
|
||||
|
||||
::-webkit-scrollbar-corner
|
||||
background transparent
|
||||
|
||||
/* These rules are for scrollbar draggable panel */
|
||||
::-webkit-scrollbar-thumb
|
||||
background-color rgba(0, 0, 0, 0.6)
|
||||
box-shadow 0 0 2px rgba(0, 0, 0, 0.9) inset
|
||||
|
||||
::-webkit-scrollbar-thumb:hover
|
||||
background-color rgba(0, 0, 0, 0.8)
|
||||
|
||||
::-webkit-scrollbar-thumb:active
|
||||
background-color rgba(0, 0, 0, 1)
|
||||
|
||||
/* These rules are for buttons */
|
||||
::-webkit-scrollbar-button:start
|
||||
display none
|
||||
|
||||
::-webkit-scrollbar-button:end
|
||||
display none
|
||||
188
client/edit.js
Normal file
188
client/edit.js
Normal file
@@ -0,0 +1,188 @@
|
||||
Session.set('points-autoadd', false);
|
||||
var full_clear_required = true;
|
||||
|
||||
function add_point(latlng, num, trigger) {
|
||||
maps.lines.route.add(latlng);
|
||||
maps.markers.add(latlng, {type: 'icon', text: num});
|
||||
}
|
||||
|
||||
var num = 1;
|
||||
function new_point(latlng) {
|
||||
Meteor.call('insert_point', latlng, Session.get('route'), function () {
|
||||
add_point(latlng, num, true);
|
||||
++num;
|
||||
$('.sidebar').animate({scrollTop: $('.sidebar-inner').height()}, 'fast');
|
||||
});
|
||||
}
|
||||
|
||||
// --- Route management ---
|
||||
|
||||
Template.frontpage.editing_route_name = function () {
|
||||
return Session.equals('editing_route_name', this._id);
|
||||
};
|
||||
|
||||
Template.frontpage.can_edit = function () {
|
||||
var route = Routes.findOne({_id: this._id});
|
||||
return (route && Meteor.user() && route.owner === Meteor.user()._id);
|
||||
};
|
||||
|
||||
Template.frontpage.logged_in = function () {
|
||||
return Meteor.user();
|
||||
};
|
||||
|
||||
Template.frontpage.events({
|
||||
'click div.thumbnail img': function (event, template) {
|
||||
Router.navigate('/edit/' + this._id, {trigger: true});
|
||||
},
|
||||
'click a.remove': function () {
|
||||
Points.remove({route: this._id});
|
||||
Routes.remove({_id: this._id});
|
||||
},
|
||||
'dblclick div.thumbnail span': function (event, template) {
|
||||
if (Meteor.user() && this.owner === Meteor.user()._id) {
|
||||
Session.set('editing_route_name', this._id);
|
||||
Meteor.flush();
|
||||
activateInput(template.find('#route-name'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.frontpage.events(
|
||||
okCancelEvents('#new-route', {
|
||||
ok: function (text, event) {
|
||||
Routes.insert({owner: Meteor.user()._id, name: text, first: null});
|
||||
event.target.value = "";
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Template.frontpage.events(
|
||||
okCancelEvents('#route-name', {
|
||||
ok: function (text) {
|
||||
Routes.update({_id: this._id}, {$set: {name: text}});
|
||||
Session.set('editing_route_name', null);
|
||||
},
|
||||
cancel: function () {
|
||||
Session.set('editing_route_name', null);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// --- Points ---
|
||||
|
||||
Template.points.points = function () {
|
||||
var points = [];
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
if (route) {
|
||||
var point = Points.findOne({_id: route.first});
|
||||
while (point !== undefined) {
|
||||
points.push(point);
|
||||
point = Points.findOne({_id: point.next});
|
||||
}
|
||||
}
|
||||
return points;
|
||||
};
|
||||
|
||||
Template.points.autoadd = function () {
|
||||
return Session.get('points-autoadd');
|
||||
};
|
||||
|
||||
Template.points.can_edit = function () {
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
return (route && Meteor.user() && route.owner === Meteor.user()._id);
|
||||
};
|
||||
|
||||
Template.points.helpers(helpers);
|
||||
|
||||
Template.points.events({
|
||||
'click li.point': function () {
|
||||
maps.travel(this._id, {center: true});
|
||||
},
|
||||
'click a.remove': function () {
|
||||
Meteor.call('remove_point', this._id, function () {
|
||||
full_clear_required = true;
|
||||
});
|
||||
},
|
||||
'click .new-point button': function () {
|
||||
new_point(maps.getLatLng());
|
||||
},
|
||||
'change #points-autoadd': function (event) {
|
||||
Session.set('points-autoadd', event.currentTarget.checked);
|
||||
}
|
||||
});
|
||||
|
||||
Template.editing.owner = function () {
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
var user = Meteor.users.findOne({_id: route.owner});
|
||||
if (!user) return;
|
||||
return user.profile.name;
|
||||
};
|
||||
|
||||
function init_edit() {
|
||||
|
||||
Meteor.autorun(function () {
|
||||
if (Session.equals('page', 'edit') && Session.get('route')) {
|
||||
debug('sivu tai route vaihtui, route: ' + Session.get('route'));
|
||||
full_clear_required = true;
|
||||
|
||||
Meteor.deps.isolate(function () {
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
if (route) {
|
||||
maps.travel(route.first, {route: true});
|
||||
} else {
|
||||
maps.default_pos();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.autosubscribe(function () {
|
||||
Session.set('points-autoadd', false);
|
||||
|
||||
if (Session.equals('page', 'edit') && Session.get('route')) {
|
||||
if (full_clear_required) {
|
||||
Meteor.deps.isolate(function () {
|
||||
debug('full clear');
|
||||
|
||||
maps.lines.route.clear();
|
||||
maps.markers.clear();
|
||||
num = 1;
|
||||
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
if (route) {
|
||||
var point = Points.findOne({_id: route.first});
|
||||
while (point !== undefined) {
|
||||
add_point(point.latlng, num, false);
|
||||
|
||||
point = Points.findOne({_id: point.next});
|
||||
++num;
|
||||
}
|
||||
full_clear_required = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function autoadd_listener() {
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
if (Session.get('points-autoadd') && route && Meteor.user() && Meteor.user()._id === route.owner) {
|
||||
var latlng = maps.getLatLng();
|
||||
|
||||
// Do not add duplicate points.
|
||||
if (Points.findOne({route: route._id, latlng: latlng}) !== undefined) return;
|
||||
|
||||
new_point(latlng);
|
||||
}
|
||||
}
|
||||
|
||||
window.autoadd_listener = null;
|
||||
Meteor.autosubscribe(function () {
|
||||
if (Session.get('points-autoadd')) {
|
||||
maps.listeners.add('points-autoadd', 'street', 'position_changed', autoadd_listener);
|
||||
} else {
|
||||
maps.listeners.remove('points-autoadd');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
106
client/jquery.hotkeys.js
Normal file
106
client/jquery.hotkeys.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* jQuery Hotkeys Plugin
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Based upon the plugin by Tzury Bar Yochay:
|
||||
* http://github.com/tzuryby/hotkeys
|
||||
*
|
||||
* Original idea by:
|
||||
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
|
||||
*/
|
||||
|
||||
(function(jQuery){
|
||||
|
||||
jQuery.hotkeys = {
|
||||
version: "0.8+",
|
||||
|
||||
specialKeys: {
|
||||
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
|
||||
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
|
||||
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
|
||||
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
|
||||
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
|
||||
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
|
||||
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 188: ",", 190: ".",
|
||||
191: "/", 224: "meta"
|
||||
},
|
||||
|
||||
shiftNums: {
|
||||
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
|
||||
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
|
||||
".": ">", "/": "?", "\\": "|"
|
||||
}
|
||||
};
|
||||
|
||||
function keyHandler( handleObj ) {
|
||||
|
||||
var origHandler = handleObj.handler,
|
||||
//use namespace as keys so it works with event delegation as well
|
||||
//will also allow removing listeners of a specific key combination
|
||||
//and support data objects
|
||||
keys = (handleObj.namespace || "").toLowerCase().split(" ");
|
||||
keys = jQuery.map(keys, function(key) { return key.split("."); });
|
||||
|
||||
//no need to modify handler if no keys specified
|
||||
if (keys.length === 1 && (keys[0] === "" || keys[0] === "autocomplete")) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleObj.handler = function( event ) {
|
||||
// Don't fire in text-accepting inputs that we didn't directly bind to
|
||||
// important to note that $.fn.prop is only available on jquery 1.6+
|
||||
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
|
||||
event.target.type === "text" || $(event.target).prop('contenteditable') == 'true' )) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keypress represents characters, not special keys
|
||||
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
|
||||
character = String.fromCharCode( event.which ).toLowerCase(),
|
||||
key, modif = "", possible = {};
|
||||
|
||||
// check combinations (alt|ctrl|shift+anything)
|
||||
if ( event.altKey && special !== "alt" ) {
|
||||
modif += "alt_";
|
||||
}
|
||||
|
||||
if ( event.ctrlKey && special !== "ctrl" ) {
|
||||
modif += "ctrl_";
|
||||
}
|
||||
|
||||
// TODO: Need to make sure this works consistently across platforms
|
||||
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
|
||||
modif += "meta_";
|
||||
}
|
||||
|
||||
if ( event.shiftKey && special !== "shift" ) {
|
||||
modif += "shift_";
|
||||
}
|
||||
|
||||
if ( special ) {
|
||||
possible[ modif + special ] = true;
|
||||
|
||||
} else {
|
||||
possible[ modif + character ] = true;
|
||||
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
|
||||
|
||||
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
|
||||
if ( modif === "shift_" ) {
|
||||
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( var i = 0, l = keys.length; i < l; i++ ) {
|
||||
if ( possible[ keys[i] ] ) {
|
||||
return origHandler.apply( this, arguments );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
|
||||
jQuery.event.special[ this ] = { add: keyHandler };
|
||||
});
|
||||
|
||||
})( jQuery );
|
||||
46
client/lib/helpers.js
Normal file
46
client/lib/helpers.js
Normal file
@@ -0,0 +1,46 @@
|
||||
helpers = {
|
||||
round: function (num, dig) {
|
||||
return parseFloat(num).toFixed(dig);
|
||||
},
|
||||
lat: function (latlng) {
|
||||
return latlng[0].toFixed(5);
|
||||
},
|
||||
lng: function (latlng) {
|
||||
return latlng[1].toFixed(5);
|
||||
}
|
||||
};
|
||||
|
||||
var activateInput = function (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
};
|
||||
|
||||
var okCancelEvents = function (selector, callbacks) {
|
||||
var ok = callbacks.ok || function () {};
|
||||
var cancel = callbacks.cancel || function () {};
|
||||
|
||||
var events = {};
|
||||
events['keyup '+selector+', keydown '+selector+', focusout '+selector] =
|
||||
function (evt) {
|
||||
if (evt.type === "keydown" && evt.which === 27) {
|
||||
// escape = cancel
|
||||
cancel.call(this, evt);
|
||||
|
||||
} else if (evt.type === "keyup" && evt.which === 13 ||
|
||||
evt.type === "focusout") {
|
||||
// blur/return/enter = ok/submit if non-empty
|
||||
var value = String(evt.target.value || "");
|
||||
if (value)
|
||||
ok.call(this, value, evt);
|
||||
else
|
||||
cancel.call(this, evt);
|
||||
}
|
||||
};
|
||||
return events;
|
||||
};
|
||||
|
||||
// From: https://github.com/tmeasday/meteor-deps-extensions
|
||||
Meteor.deps.isolate = function(fn) {
|
||||
var context = new Meteor.deps.Context();
|
||||
return context.run(fn);
|
||||
};
|
||||
180
client/lib/maps.js
Normal file
180
client/lib/maps.js
Normal file
@@ -0,0 +1,180 @@
|
||||
function maps_loaded() {
|
||||
|
||||
window.markers = []; // fuu
|
||||
|
||||
var Maps = function () {
|
||||
function glatlng(latlng) {
|
||||
return new google.maps.LatLng(latlng[0], latlng[1]);
|
||||
}
|
||||
|
||||
var Line = function (attr) {
|
||||
attr = attr || {};
|
||||
|
||||
var data = new google.maps.Polyline(attr);
|
||||
|
||||
return {
|
||||
clear: function () {
|
||||
data.setPath([]);
|
||||
},
|
||||
add: function (latlng) {
|
||||
data.getPath().push(glatlng(latlng));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var Markers = function (map) {
|
||||
var data = [];
|
||||
|
||||
return {
|
||||
clear: function () {
|
||||
for (var i = data.length - 1; i >= 0; --i) {
|
||||
data[i].setMap(null);
|
||||
}
|
||||
data.length = 0;
|
||||
},
|
||||
add: function (latlng, arg) {
|
||||
var attr = {
|
||||
map: map,
|
||||
position: glatlng(latlng)
|
||||
};
|
||||
|
||||
if (arg.type == 'icon') {
|
||||
attr['icon'] = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=' + arg.text + '|FF0000|000000';
|
||||
}
|
||||
|
||||
data.push(new google.maps.Marker(attr));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var Listeners = function (instances) {
|
||||
var handles = {};
|
||||
|
||||
return {
|
||||
add: function (id, instance, event, cb) {
|
||||
if (_.has(handles, id)) {
|
||||
google.maps.event.removeListener(handles[id]);
|
||||
}
|
||||
handles[id] = google.maps.event.addListener(instances[instance], event, cb);
|
||||
},
|
||||
remove: function (id) {
|
||||
google.maps.event.removeListener(handles[id]);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var street = new google.maps.StreetViewPanorama(document.getElementById("street"), {
|
||||
position: glatlng(settings.default_latlng),
|
||||
pov: settings.default_pov,
|
||||
panControl: false,
|
||||
imageDateControl: false,
|
||||
scrollwheel: false,
|
||||
zoomControl: false,
|
||||
addressControl: false
|
||||
});
|
||||
|
||||
var map = new google.maps.Map(document.getElementById("map"), {
|
||||
center: glatlng(settings.default_latlng),
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP,
|
||||
zoom: 15,
|
||||
mapTypeControl: false,
|
||||
draggable: false,
|
||||
streetView: street,
|
||||
streetViewControl: false,
|
||||
scrollwheel: false,
|
||||
zoomControl: false
|
||||
});
|
||||
window.map = map;
|
||||
|
||||
var bicycling = new google.maps.BicyclingLayer();
|
||||
bicycling.setMap(map);
|
||||
|
||||
var lines = {
|
||||
'route': new Line({
|
||||
map: map,
|
||||
strokeColor: '#51B5FF'
|
||||
}),
|
||||
'traveled': new Line({
|
||||
map: map
|
||||
})
|
||||
};
|
||||
|
||||
var markers = new Markers(map);
|
||||
|
||||
var listeners = new Listeners({
|
||||
map: map,
|
||||
street: street
|
||||
});
|
||||
|
||||
return {
|
||||
lines: lines,
|
||||
markers: markers,
|
||||
listeners: listeners,
|
||||
getLatLng: function () {
|
||||
return [street.getPosition().lat(), street.getPosition().lng()];
|
||||
},
|
||||
mode: function (mode) {
|
||||
if (mode == 'edit') {
|
||||
map.setOptions({
|
||||
scrollwheel: true,
|
||||
draggable: true,
|
||||
streetViewControl: true
|
||||
});
|
||||
street.setOptions({
|
||||
clickToGo: true,
|
||||
linksControl: true
|
||||
});
|
||||
} else if (mode == 'sim') {
|
||||
map.setOptions({
|
||||
scrollwheel: false,
|
||||
draggable: false,
|
||||
streetViewControl: false
|
||||
});
|
||||
street.setOptions({
|
||||
clickToGo: false,
|
||||
linksControl: false
|
||||
});
|
||||
}
|
||||
},
|
||||
default_pos: function () {
|
||||
street.setPosition(glatlng(settings.default_latlng));
|
||||
street.setPov(settings.default_pov);
|
||||
},
|
||||
/*
|
||||
* Travel to a location.
|
||||
* Updates streetview.
|
||||
* Optionally centers the map.
|
||||
*/
|
||||
travel: function (point_id, arg) {
|
||||
arg = arg || {};
|
||||
_.defaults(arg, {
|
||||
route: false
|
||||
});
|
||||
|
||||
var point = Points.findOne({_id: point_id});
|
||||
if (point) {
|
||||
street.setPosition(glatlng(point.latlng));
|
||||
map.setCenter(glatlng(point.latlng));
|
||||
|
||||
if (point.heading) street.setPov({zoom: 1, pitch: 0, heading: point.heading});
|
||||
if (arg.route) lines.traveled.add(point.latlng);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
window.maps = new Maps();
|
||||
init_main();
|
||||
init_sim();
|
||||
init_edit();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = "http://maps.googleapis.com/maps/api/js?key=" + settings.maps_key + "&sensor=false&callback=maps_loaded";
|
||||
document.body.appendChild(script);
|
||||
};
|
||||
35
client/lib/settings.js
Normal file
35
client/lib/settings.js
Normal file
@@ -0,0 +1,35 @@
|
||||
window.settings = {
|
||||
'default_latlng': [61.501043, 23.763035],
|
||||
'default_pov': {
|
||||
heading: 200,
|
||||
pitch: 0,
|
||||
zoom: 1
|
||||
},
|
||||
'debug': false,
|
||||
'maps_key': 'AIzaSyDtGhiAnSdg9TaGZC_daNcQe43BS8Ws7Iw',
|
||||
'staticmaps_key': 'AIzaSyDtGhiAnSdg9TaGZC_daNcQe43BS8Ws7Iw'
|
||||
};
|
||||
|
||||
window.c = function () {
|
||||
return Math.PI * localStorage.diameter * 2.54 / 100;
|
||||
};
|
||||
|
||||
if (_.has(localStorage, 'diameter') && !_.isNumber(localStorage.diameter)) {
|
||||
delete localStorage.diameter;
|
||||
}
|
||||
|
||||
|
||||
if (_.has(localStorage, 'multiplier') && !_.isNumber(localStorage.multiplier)) {
|
||||
delete localStorage.multiplier;
|
||||
}
|
||||
|
||||
_.defaults(localStorage, {
|
||||
diameter: 28,
|
||||
multiplier: 2.5
|
||||
});
|
||||
|
||||
window.debug = function () {
|
||||
if (this.console && settings.debug) {
|
||||
console.log(Array.prototype.slice.call(arguments));
|
||||
}
|
||||
};
|
||||
185
client/main.js
Normal file
185
client/main.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// --- Body classes ---
|
||||
Meteor.autosubscribe(function () {
|
||||
if (Session.equals('page', 'edit')) {
|
||||
$("body").addClass('show-sidebar');
|
||||
} else {
|
||||
$("body").removeClass('show-sidebar');
|
||||
}
|
||||
|
||||
if (Session.equals('page', 'sim') || Session.equals('page', 'edit')) {
|
||||
$("body").addClass('show-map');
|
||||
} else {
|
||||
$("body").removeClass('show-map');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
if (Session.equals('page', 'edit')) {
|
||||
$("body").addClass('show-sidebar');
|
||||
}
|
||||
|
||||
if (Session.equals('page', 'sim') || Session.equals('page', 'edit')) {
|
||||
$("body").addClass('show-map');
|
||||
}
|
||||
});
|
||||
|
||||
// --- Main ---
|
||||
|
||||
Template.main.frontpage = function () {
|
||||
return Session.equals('page', 'frontpage');
|
||||
};
|
||||
|
||||
Template.main.frontpage_edit = function () {
|
||||
return Session.equals('page', 'frontpage_edit');
|
||||
};
|
||||
|
||||
Template.main.editing = function () {
|
||||
if (Session.equals('page', 'edit')) {
|
||||
return Routes.findOne({_id: Session.get('route')});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Template.main.help = function () {
|
||||
return Session.equals('page', 'help');
|
||||
};
|
||||
|
||||
Template.main.settings = function () {
|
||||
return Session.equals('page', 'settings');
|
||||
};
|
||||
|
||||
Template.main.sim = function () {
|
||||
if (Session.equals('page', 'sim')) {
|
||||
return Routes.findOne({_id: Session.get('route')});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// --- Frontpage ---
|
||||
|
||||
Template.frontpage.routes = function () {
|
||||
return Routes.find();
|
||||
};
|
||||
|
||||
Template.frontpage.editing = function () {
|
||||
return Session.equals('page', 'frontpage_edit');
|
||||
};
|
||||
|
||||
Template.frontpage.staticmap = function () {
|
||||
return 'http://maps.googleapis.com/maps/api/staticmap?path=enc:' + this.path + '&size=360x270&key=' + settings.staticmaps_key + '&sensor=false';
|
||||
};
|
||||
|
||||
Template.frontpage.helpers({
|
||||
'km': function (m) {
|
||||
return (m / 1000).toFixed(2);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Settings ---
|
||||
Template.settings.settings = function () {
|
||||
return localStorage;
|
||||
};
|
||||
|
||||
Template.settings.events({
|
||||
'change #settings_multiplier': function (event) {
|
||||
localStorage.setItem('multiplier', parseFloat(event.target.value));
|
||||
},
|
||||
'change #settings_diameter': function (event) {
|
||||
localStorage.setItem('diameter', parseInt(event.target.value, 10));
|
||||
}
|
||||
});
|
||||
|
||||
// --- Keybinds ---
|
||||
|
||||
$(document).bind('keydown.esc', function () {
|
||||
Router.navigate('/', {trigger: true});
|
||||
});
|
||||
|
||||
// --- Routes ---
|
||||
|
||||
$(document).on("click", "a[href^='/']", function (event) {
|
||||
var href = $(event.currentTarget).attr('href');
|
||||
|
||||
// chain 'or's for other black list routes
|
||||
var passThrough = href.indexOf('sign_out') >= 0;
|
||||
|
||||
// Allow shift+click for new tabs, etc.
|
||||
if (!passThrough && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
||||
// Remove leading slashes and hash bangs (backward compatablility)
|
||||
var url = href.replace('/^\//', '').replace('#!', '');
|
||||
|
||||
// Instruct Backbone to trigger routing events
|
||||
Router.navigate(url, {trigger: true});
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
var RoutesRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
"": "index",
|
||||
"help": "help",
|
||||
"settings": "settings",
|
||||
"edit": "index_edit",
|
||||
":route_id": "sim",
|
||||
"edit/:route_id": "edit"
|
||||
},
|
||||
index: function () {
|
||||
debug('frontpage');
|
||||
Session.set('page', 'frontpage');
|
||||
Session.set('route', null);
|
||||
},
|
||||
help: function () {
|
||||
debug('frontpage');
|
||||
Session.set('page', 'help');
|
||||
Session.set('route', null);
|
||||
},
|
||||
settings: function () {
|
||||
debug('frontpage');
|
||||
Session.set('page', 'settings');
|
||||
Session.set('route', null);
|
||||
},
|
||||
index_edit: function () {
|
||||
debug('frontpage edit');
|
||||
Session.set('page', 'frontpage_edit');
|
||||
Session.set('route', null);
|
||||
},
|
||||
sim: function (route_id) {
|
||||
debug('simulation, route: ' + route_id);
|
||||
Session.set('page', 'sim');
|
||||
Session.set('route', route_id);
|
||||
},
|
||||
edit: function (route_id) {
|
||||
debug('edit, route: ' + route_id);
|
||||
Session.set('page', 'edit');
|
||||
Session.set('route', route_id);
|
||||
}
|
||||
});
|
||||
|
||||
Router = new RoutesRouter();
|
||||
|
||||
Meteor.startup(function () {
|
||||
Backbone.history.start({pushState: true});
|
||||
});
|
||||
|
||||
function init_main() {
|
||||
Meteor.autosubscribe(function () {
|
||||
// When changing page
|
||||
|
||||
maps.markers.clear();
|
||||
maps.lines.traveled.clear();
|
||||
maps.lines.route.clear();
|
||||
|
||||
if (Session.equals('page', 'frontpage') || Session.equals('page', 'frontpage_edit')) {
|
||||
maps.default_pos();
|
||||
}
|
||||
|
||||
if (Session.equals('page', 'edit')) {
|
||||
maps.mode('edit');
|
||||
} else {
|
||||
maps.mode('sim');
|
||||
}
|
||||
});
|
||||
}
|
||||
101
client/main.sim.js
Normal file
101
client/main.sim.js
Normal file
@@ -0,0 +1,101 @@
|
||||
window.point = null;
|
||||
window.traveled = 0;
|
||||
|
||||
var createRingBuffer = function (length){
|
||||
var pointer = 0, buffer = [], sum = 0;
|
||||
|
||||
return {
|
||||
push: function (item) {
|
||||
if (buffer[pointer] > 0) {
|
||||
sum -= buffer[pointer];
|
||||
if (sum <= 0) sum = 0;
|
||||
}
|
||||
buffer[pointer] = item;
|
||||
sum += item;
|
||||
pointer = (length + pointer + 1) % length;
|
||||
},
|
||||
sum: function () {
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var revs = 0;
|
||||
$(document).bind('keydown.space', function () {
|
||||
revs += 1;
|
||||
|
||||
var dist = localStorage['multiplier'] * c();
|
||||
Session.set('distance', Session.get('distance') + dist);
|
||||
window.traveled += dist;
|
||||
|
||||
if (window.traveled >= window.point.distance) {
|
||||
var next = Points.findOne({_id: window.point.next});
|
||||
|
||||
// Go to next point, if one exists
|
||||
if (next) {
|
||||
window.traveled -= next.distance;
|
||||
|
||||
maps.travel(next._id, {route: true});
|
||||
window.point = next;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function speedo() {
|
||||
speed_buffer.push(revs * c());
|
||||
revs = 0;
|
||||
|
||||
Session.set('speed', speed_buffer.sum() / 5);
|
||||
}
|
||||
|
||||
setInterval(speedo, 500);
|
||||
|
||||
// 5sec, 2 values / sec.
|
||||
var speed_buffer = createRingBuffer(5 * 2);
|
||||
var speed_sum = 0;
|
||||
var speed_avg = 0;
|
||||
|
||||
Template.sim.speed = function () {
|
||||
return Session.get('speed');
|
||||
};
|
||||
|
||||
Template.sim.distance = function () {
|
||||
return Session.get('distance');
|
||||
};
|
||||
|
||||
Template.sim.helpers({
|
||||
kmh: function (ms) {
|
||||
return (ms * 60 * 60 / 1000).toFixed(1);
|
||||
},
|
||||
km: function (m) {
|
||||
return (m / 1000).toFixed(2);
|
||||
}
|
||||
});
|
||||
|
||||
function init_sim() {
|
||||
|
||||
Meteor.autosubscribe(function () {
|
||||
if (Session.equals('page', 'sim')) {
|
||||
var route = Routes.findOne({_id: Session.get('route')});
|
||||
if (route) {
|
||||
Session.set('distance', 0);
|
||||
Session.set('speed', 0);
|
||||
window.traveled = 0;
|
||||
maps.lines.traveled.clear();
|
||||
maps.lines.route.clear();
|
||||
|
||||
// Siirytään reitin alkuun
|
||||
window.point = Points.findOne({_id: route.first});
|
||||
maps.travel(route.first, {route: true});
|
||||
|
||||
// Show full route on map
|
||||
var p = window.point;
|
||||
while (p !== undefined) {
|
||||
maps.lines.route.add(p.latlng);
|
||||
p = Points.findOne({_id: p.next});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user