mirror of
https://github.com/Ekokumppanit/Lentolaskuri.git
synced 2026-01-26 03:03:58 +00:00
Initial commit
This commit is contained in:
29
.editorconfig
Normal file
29
.editorconfig
Normal file
@@ -0,0 +1,29 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.sublime-project]
|
||||
indent_style = tab
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
.tmp
|
||||
app/components
|
||||
*.sublime-workspace
|
||||
26
.jshintrc
Normal file
26
.jshintrc
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"es5": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"camelcase": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "single",
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"unused": false,
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"smarttabs": true,
|
||||
"globals": {
|
||||
"$": true,
|
||||
"define": true
|
||||
}
|
||||
}
|
||||
320
Gruntfile.js
Normal file
320
Gruntfile.js
Normal file
@@ -0,0 +1,320 @@
|
||||
'use strict';
|
||||
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
|
||||
var mountFolder = function (connect, dir) {
|
||||
return connect.static(require('path').resolve(dir));
|
||||
};
|
||||
|
||||
module.exports = function (grunt) {
|
||||
// load all grunt tasks
|
||||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
|
||||
|
||||
// configurable paths
|
||||
var yeomanConfig = {
|
||||
app: 'app',
|
||||
dist: 'dist'
|
||||
};
|
||||
|
||||
grunt.initConfig({
|
||||
yeoman: yeomanConfig,
|
||||
watch: {
|
||||
less: {
|
||||
files: ['<%= yeoman.app %>/styles/*.less'],
|
||||
tasks: ['less']
|
||||
},
|
||||
livereload: {
|
||||
files: [
|
||||
'<%= yeoman.app %>/*.html',
|
||||
'{.tmp,<%= yeoman.app %>}/styles/*.css',
|
||||
'{.tmp,<%= yeoman.app %>}/scripts/**/*.js',
|
||||
'<%= yeoman.app %>/images/*.{png,jpg,jpeg,webp}'
|
||||
],
|
||||
tasks: ['livereload']
|
||||
},
|
||||
handlebars: {
|
||||
files: [
|
||||
'app/templates/**/*.hbs'
|
||||
],
|
||||
tasks: ['handlebars', 'livereload']
|
||||
}
|
||||
},
|
||||
handlebars: {
|
||||
compile: {
|
||||
files: {
|
||||
'.tmp/scripts/templates.js': [
|
||||
'app/templates/**/*.hbs'
|
||||
]
|
||||
},
|
||||
options: {
|
||||
// namespace: false,
|
||||
amd: true,
|
||||
processName: function (filename) {
|
||||
return filename.replace(/^app\/templates\//, '').replace(/\.hbs/, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
options: {
|
||||
port: 9000,
|
||||
// change this to '0.0.0.0' to access the server from outside
|
||||
hostname: 'localhost'
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
middleware: function (connect) {
|
||||
return [
|
||||
lrSnippet,
|
||||
mountFolder(connect, '.tmp'),
|
||||
mountFolder(connect, 'app')
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
middleware: function (connect) {
|
||||
return [
|
||||
mountFolder(connect, 'dist')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
open: {
|
||||
server: {
|
||||
url: 'http://localhost:<%= connect.options.port %>'
|
||||
}
|
||||
},
|
||||
clean: {
|
||||
dist: ['.tmp', '<%= yeoman.dist %>/*'],
|
||||
server: '.tmp'
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'<%= yeoman.app %>/scripts/*.js'
|
||||
]
|
||||
},
|
||||
less: {
|
||||
dist: {
|
||||
options: {
|
||||
paths: ['app/components'],
|
||||
yuicompress: true,
|
||||
strictUnits: false,
|
||||
strictMaths: false
|
||||
},
|
||||
files: {
|
||||
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.less'
|
||||
}
|
||||
},
|
||||
server: {
|
||||
options: {
|
||||
paths: ['app/components'],
|
||||
strictUnits: false,
|
||||
strictMaths: false
|
||||
},
|
||||
files: {
|
||||
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.less'
|
||||
}
|
||||
}
|
||||
},
|
||||
requirejs: {
|
||||
dist: {
|
||||
// Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
|
||||
options: {
|
||||
// `name` and `out` is set by grunt-usemin
|
||||
baseUrl: 'app/scripts',
|
||||
paths: {
|
||||
'bootstrap': '../../.tmp/scripts/bootstrap',
|
||||
'Template': '../../.tmp/scripts/templates'
|
||||
},
|
||||
optimize: 'none',
|
||||
// TODO: Figure out how to make sourcemaps work with grunt-usemin
|
||||
// https://github.com/yeoman/grunt-usemin/issues/30
|
||||
//generateSourceMaps: true,
|
||||
// required to support SourceMaps
|
||||
// http://requirejs.org/docs/errors.html#sourcemapcomments
|
||||
preserveLicenseComments: false,
|
||||
useStrict: true,
|
||||
wrap: true,
|
||||
include: [
|
||||
'libs/gmaps'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
useminPrepare: {
|
||||
html: '<%= yeoman.app %>/index.html',
|
||||
options: {
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}
|
||||
},
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/*.html'],
|
||||
css: ['<%= yeoman.dist %>/styles/*.css'],
|
||||
options: {
|
||||
dirs: ['<%= yeoman.dist %>']
|
||||
}
|
||||
},
|
||||
imagemin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '*.{png,jpg,jpeg}',
|
||||
dest: '<%= yeoman.dist %>/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= yeoman.dist %>/styles/main.css': [
|
||||
'.tmp/styles/*.css',
|
||||
'<%= yeoman.app %>/styles/*.css'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
htmlmin: {
|
||||
dist: {
|
||||
options: {
|
||||
/*removeCommentsFromCDATA: true,
|
||||
// https://github.com/yeoman/grunt-usemin/issues/44
|
||||
//collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true*/
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: '*.html',
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}]
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>',
|
||||
src: [
|
||||
'api/*',
|
||||
'*.{ico,txt}',
|
||||
'.htaccess',
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
flatten: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>/font',
|
||||
src: [
|
||||
'components/font-awesome/build/assets/font-awesome/font/*.{ttf,woff,otf,eot,svg}'
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
flatten: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>/styles',
|
||||
src: [
|
||||
'components/select2/*.{png,gif}'
|
||||
]
|
||||
}]
|
||||
},
|
||||
livereload: {
|
||||
files: [{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '.tmp/font',
|
||||
src: [
|
||||
'components/font-awesome/build/assets/font-awesome/font/*.{ttf,woff,otf,eot,svg}'
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
flatten: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '.tmp/styles',
|
||||
src: [
|
||||
'components/select2/*.{png,gif}'
|
||||
]
|
||||
}]
|
||||
}
|
||||
},
|
||||
bower: {
|
||||
all: {
|
||||
rjsConfig: '<%= yeoman.app %>/scripts/main.js'
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
bootstrap: {
|
||||
src: [
|
||||
'app/components/bootstrap/js/bootstrap-transition.js',
|
||||
'app/components/bootstrap/js/bootstrap-alert.js',
|
||||
'app/components/bootstrap/js/bootstrap-button.js',
|
||||
'app/components/bootstrap/js/bootstrap-carousel.js',
|
||||
'app/components/bootstrap/js/bootstrap-collapse.js',
|
||||
'app/components/bootstrap/js/bootstrap-dropdown.js',
|
||||
'app/components/bootstrap/js/bootstrap-modal.js',
|
||||
'app/components/bootstrap/js/bootstrap-tooltip.js',
|
||||
'app/components/bootstrap/js/bootstrap-popover.js',
|
||||
'app/components/bootstrap/js/bootstrap-scrollspy.js',
|
||||
'app/components/bootstrap/js/bootstrap-tab.js',
|
||||
'app/components/bootstrap/js/bootstrap-typeahead.js',
|
||||
'app/components/bootstrap/js/bootstrap-affix.js'
|
||||
],
|
||||
dest: '.tmp/scripts/bootstrap.js'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.renameTask('regarde', 'watch');
|
||||
|
||||
grunt.registerTask('server', function (target) {
|
||||
if (target === 'dist') {
|
||||
return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
|
||||
}
|
||||
|
||||
grunt.task.run([
|
||||
'clean:server',
|
||||
'less:server',
|
||||
'handlebars',
|
||||
'concat',
|
||||
'copy:livereload',
|
||||
'livereload-start',
|
||||
'connect:livereload',
|
||||
'open',
|
||||
'watch'
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'clean:dist',
|
||||
'less:dist',
|
||||
'handlebars',
|
||||
'concat',
|
||||
'useminPrepare',
|
||||
'requirejs',
|
||||
'imagemin',
|
||||
'htmlmin',
|
||||
'concat',
|
||||
'cssmin',
|
||||
'uglify',
|
||||
'copy',
|
||||
'usemin'
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'jshint',
|
||||
'build'
|
||||
]);
|
||||
};
|
||||
543
app/.htaccess
Normal file
543
app/.htaccess
Normal file
@@ -0,0 +1,543 @@
|
||||
# Apache configuration file
|
||||
# httpd.apache.org/docs/2.2/mod/quickreference.html
|
||||
|
||||
# Note .htaccess files are an overhead, this logic should be in your Apache
|
||||
# config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html
|
||||
|
||||
# Techniques in here adapted from all over, including:
|
||||
# Kroc Camen: camendesign.com/.htaccess
|
||||
# perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/
|
||||
# Sample .htaccess file of CMS MODx: modxcms.com
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Better website experience for IE users
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Force the latest IE version, in various cases when it may fall back to IE7 mode
|
||||
# github.com/rails/rails/commit/123eb25#commitcomment-118920
|
||||
# Use ChromeFrame if it's installed for a better experience for the poor IE folk
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-UA-Compatible "IE=Edge,chrome=1"
|
||||
# mod_headers can't match by content-type, but we don't want to send this header on *everything*...
|
||||
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webm|webp|woff|xml|xpi)$">
|
||||
Header unset X-UA-Compatible
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Cross-domain AJAX requests
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve cross-domain Ajax requests, disabled by default.
|
||||
# enable-cors.org
|
||||
# code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Access-Control-Allow-Origin "*"
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# CORS-enabled images (@crossorigin)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Send CORS headers if browsers request them; enabled by default for images.
|
||||
# developer.mozilla.org/en/CORS_Enabled_Image
|
||||
# blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
||||
# hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
|
||||
# wiki.mozilla.org/Security/Reviews/crossoriginAttribute
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
# mod_headers, y u no match by Content-Type?!
|
||||
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
|
||||
SetEnvIf Origin ":" IS_CORS
|
||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Webfont access
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow access from all domains for webfonts.
|
||||
# Alternatively you could only whitelist your
|
||||
# subdomains like "subdomain.example.com".
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Proper MIME type for all files
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# JavaScript
|
||||
# Normalize to standard type (it's sniffed in IE anyways)
|
||||
# tools.ietf.org/html/rfc4329#section-7.2
|
||||
AddType application/javascript js jsonp
|
||||
AddType application/json json
|
||||
|
||||
# Audio
|
||||
AddType audio/mp4 m4a f4a f4b
|
||||
AddType audio/ogg oga ogg
|
||||
|
||||
# Video
|
||||
AddType video/mp4 mp4 m4v f4v f4p
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
|
||||
# SVG
|
||||
# Required for svg webfonts on iPad
|
||||
# twitter.com/FontSquirrel/status/14855840545
|
||||
AddType image/svg+xml svg svgz
|
||||
AddEncoding gzip svgz
|
||||
|
||||
# Webfonts
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
AddType application/x-font-ttf ttf ttc
|
||||
AddType application/x-font-woff woff
|
||||
AddType font/opentype otf
|
||||
|
||||
# Assorted types
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-shockwave-flash swf
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType application/xml rss atom xml rdf
|
||||
AddType image/webp webp
|
||||
AddType image/x-icon ico
|
||||
AddType text/cache-manifest appcache manifest
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
AddType text/x-vcard vcf
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Allow concatenation from within specific js and css files
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# e.g. Inside of script.combined.js you could have
|
||||
# <!--#include file="libs/jquery-1.5.0.min.js" -->
|
||||
# <!--#include file="plugins/jquery.idletimer.js" -->
|
||||
# and they would be included into this single file.
|
||||
|
||||
# This is not in use in the boilerplate as it stands. You may
|
||||
# choose to use this technique if you do not have a build process.
|
||||
|
||||
#<FilesMatch "\.combined\.js$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES application/javascript application/json
|
||||
# SetOutputFilter INCLUDES
|
||||
#</FilesMatch>
|
||||
|
||||
#<FilesMatch "\.combined\.css$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES text/css
|
||||
# SetOutputFilter INCLUDES
|
||||
#</FilesMatch>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Gzip compression
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
||||
# Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Compress all output labeled with one of the following MIME-types
|
||||
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
|
||||
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines as
|
||||
# `AddOutputFilterByType` is still in the core directives)
|
||||
<IfModule mod_filter.c>
|
||||
AddOutputFilterByType DEFLATE application/atom+xml \
|
||||
application/javascript \
|
||||
application/json \
|
||||
application/rss+xml \
|
||||
application/vnd.ms-fontobject \
|
||||
application/x-font-ttf \
|
||||
application/xhtml+xml \
|
||||
application/xml \
|
||||
font/opentype \
|
||||
image/svg+xml \
|
||||
image/x-icon \
|
||||
text/css \
|
||||
text/html \
|
||||
text/plain \
|
||||
text/x-component \
|
||||
text/xml
|
||||
</IfModule>
|
||||
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Expires headers (for better cache control)
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# These are pretty far-future expires headers.
|
||||
# They assume you control versioning with filename-based cache busting
|
||||
# Additionally, consider that outdated proxies may miscache
|
||||
# www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
|
||||
|
||||
# If you don't use filenames to version, lower the CSS and JS to something like
|
||||
# "access plus 1 week".
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive on
|
||||
|
||||
# Perhaps better to whitelist expires rules? Perhaps.
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
# cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5)
|
||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
||||
|
||||
# Your document html
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
|
||||
# Data
|
||||
ExpiresByType application/json "access plus 0 seconds"
|
||||
ExpiresByType application/xml "access plus 0 seconds"
|
||||
ExpiresByType text/xml "access plus 0 seconds"
|
||||
|
||||
# Feed
|
||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
||||
|
||||
# Favicon (cannot be renamed)
|
||||
ExpiresByType image/x-icon "access plus 1 week"
|
||||
|
||||
# Media: images, video, audio
|
||||
ExpiresByType audio/ogg "access plus 1 month"
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType video/mp4 "access plus 1 month"
|
||||
ExpiresByType video/ogg "access plus 1 month"
|
||||
ExpiresByType video/webm "access plus 1 month"
|
||||
|
||||
# HTC files (css3pie)
|
||||
ExpiresByType text/x-component "access plus 1 month"
|
||||
|
||||
# Webfonts
|
||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
||||
ExpiresByType application/x-font-woff "access plus 1 month"
|
||||
ExpiresByType font/opentype "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
|
||||
# CSS and JavaScript
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Prevent mobile network providers from modifying your site
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# The following header prevents modification of your code over 3G on some
|
||||
# European providers.
|
||||
# This is the official 'bypass' suggested by O2 in the UK.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Cache-Control "no-transform"
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ETag removal
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# FileETag None is not enough for every server.
|
||||
<IfModule mod_headers.c>
|
||||
Header unset ETag
|
||||
</IfModule>
|
||||
|
||||
# Since we're sending far-future expires, we don't need ETags for
|
||||
# static content.
|
||||
# developer.yahoo.com/performance/rules.html#etags
|
||||
FileETag None
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Stop screen flicker in IE on CSS rollovers
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# The following directives stop screen flicker in IE on CSS rollovers - in
|
||||
# combination with the "ExpiresByType" rules for images (see above).
|
||||
|
||||
# BrowserMatch "MSIE" brokenvary=1
|
||||
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
|
||||
# BrowserMatch "Opera" !brokenvary
|
||||
# SetEnvIf brokenvary 1 force-no-vary
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Set Keep-Alive Header
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Keep-Alive allows the server to send multiple requests through one
|
||||
# TCP-connection. Be aware of possible disadvantages of this setting. Turn on
|
||||
# if you serve a lot of static content.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Connection Keep-Alive
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Cookie setting from iframes
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow cookies to be set from iframes (for IE only)
|
||||
# If needed, specify a path or regex in the Location directive.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Start rewrite engine
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Turning on the rewrite engine is necessary for the following rules and
|
||||
# features. FollowSymLinks must be enabled for this to work.
|
||||
|
||||
# Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN
|
||||
# If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where
|
||||
# 'foo' is your directory.
|
||||
|
||||
# If your web host doesn't allow the FollowSymlinks option, you may need to
|
||||
# comment it out and use `Options +SymLinksIfOwnerMatch`, but be aware of the
|
||||
# performance impact: http://goo.gl/Mluzd
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
Options +FollowSymlinks
|
||||
# Options +SymLinksIfOwnerMatch
|
||||
RewriteEngine On
|
||||
# RewriteBase /
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Suppress or force the "www." at the beginning of URLs
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# The same content should never be available under two different URLs -
|
||||
# especially not with and without "www." at the beginning, since this can cause
|
||||
# SEO problems (duplicate content). That's why you should choose one of the
|
||||
# alternatives and redirect the other one.
|
||||
|
||||
# By default option 1 (no "www.") is activated.
|
||||
# no-www.org/faq.php?q=class_b
|
||||
|
||||
# If you'd prefer to use option 2, just comment out all option 1 lines
|
||||
# and uncomment option 2.
|
||||
|
||||
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Option 1:
|
||||
# Rewrite "www.example.com -> example.com".
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTPS} !=on
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Option 2:
|
||||
# Rewrite "example.com -> www.example.com".
|
||||
# Be aware that the following rule might not be a good idea if you use "real"
|
||||
# subdomains for certain parts of your website.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{HTTPS} !=on
|
||||
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
|
||||
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Built-in filename-based cache busting
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# If you're not using the build script to manage your filename version revving,
|
||||
# you might want to consider enabling this, which will route requests for
|
||||
# `/css/style.20110203.css` to `/css/style.css`.
|
||||
|
||||
# To understand why this is important and a better idea than all.css?v1231,
|
||||
# please refer to the bundled documentation about `.htaccess`.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Prevent SSL cert warnings
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent
|
||||
# https://www.example.com when your cert only allows https://secure.example.com
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{SERVER_PORT} !^443
|
||||
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Prevent 404 errors for non-existing redirected folders
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# without -MultiViews, Apache will give a 404 for a rewrite if a folder of the
|
||||
# same name does not exist.
|
||||
# webmasterworld.com/apache/3808792.htm
|
||||
|
||||
Options -MultiViews
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Custom 404 page
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# You can add custom pages to handle 500 or 403 pretty easily, if you like.
|
||||
# If you are hosting your site in subdirectory, adjust this accordingly
|
||||
# e.g. ErrorDocument 404 /subdir/404.html
|
||||
ErrorDocument 404 /404.html
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# UTF-8 encoding
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Use UTF-8 encoding for anything served text/plain or text/html
|
||||
AddDefaultCharset utf-8
|
||||
|
||||
# Force UTF-8 for a number of file formats
|
||||
AddCharset utf-8 .atom .css .js .json .rss .vtt .xml
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# A little more security
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# To avoid displaying the exact version number of Apache being used, add the
|
||||
# following to httpd.conf (it will not work in .htaccess):
|
||||
# ServerTokens Prod
|
||||
|
||||
# "-Indexes" will have Apache block users from browsing folders without a
|
||||
# default document Usually you should leave this activated, because you
|
||||
# shouldn't allow everybody to surf through every folder on your server (which
|
||||
# includes rather private places like CMS system folders).
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
||||
# Block access to "hidden" directories or files whose names begin with a
|
||||
# period. This includes directories used by version control systems such as
|
||||
# Subversion or Git.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{SCRIPT_FILENAME} -d [OR]
|
||||
RewriteCond %{SCRIPT_FILENAME} -f
|
||||
RewriteRule "(^|/)\." - [F]
|
||||
</IfModule>
|
||||
|
||||
# Block access to backup and source files. These files may be left by some
|
||||
# text/html editors and pose a great security danger, when anyone can access
|
||||
# them.
|
||||
<FilesMatch "(\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|swp)|~)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</FilesMatch>
|
||||
|
||||
# If your server is not already configured as such, the following directive
|
||||
# should be uncommented in order to set PHP's register_globals option to OFF.
|
||||
# This closes a major security hole that is abused by most XSS (cross-site
|
||||
# scripting) attacks. For more information: http://php.net/register_globals
|
||||
#
|
||||
# IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS:
|
||||
#
|
||||
# Your server does not allow PHP directives to be set via .htaccess. In that
|
||||
# case you must make this change in your php.ini file instead. If you are
|
||||
# using a commercial web host, contact the administrators for assistance in
|
||||
# doing this. Not all servers allow local php.ini files, and they should
|
||||
# include all PHP configurations (not just this one), or you will effectively
|
||||
# reset everything to PHP defaults. Consult www.php.net for more detailed
|
||||
# information about setting PHP directives.
|
||||
|
||||
# php_flag register_globals Off
|
||||
|
||||
# Rename session cookie to something else, than PHPSESSID
|
||||
# php_value session.name sid
|
||||
|
||||
# Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.)
|
||||
# php_flag magic_quotes_gpc Off
|
||||
|
||||
# Do not show you are using PHP
|
||||
# Note: Move this line to php.ini since it won't work in .htaccess
|
||||
# php_flag expose_php Off
|
||||
|
||||
# Level of log detail - log all errors
|
||||
# php_value error_reporting -1
|
||||
|
||||
# Write errors to log file
|
||||
# php_flag log_errors On
|
||||
|
||||
# Do not display errors in browser (production - Off, development - On)
|
||||
# php_flag display_errors Off
|
||||
|
||||
# Do not display startup errors (production - Off, development - On)
|
||||
# php_flag display_startup_errors Off
|
||||
|
||||
# Format errors in plain text
|
||||
# Note: Leave this setting 'On' for xdebug's var_dump() output
|
||||
# php_flag html_errors Off
|
||||
|
||||
# Show multiple occurrence of error
|
||||
# php_flag ignore_repeated_errors Off
|
||||
|
||||
# Show same errors from different sources
|
||||
# php_flag ignore_repeated_source Off
|
||||
|
||||
# Size limit for error messages
|
||||
# php_value log_errors_max_len 1024
|
||||
|
||||
# Don't precede error with string (doesn't accept empty string, use whitespace if you need)
|
||||
# php_value error_prepend_string " "
|
||||
|
||||
# Don't prepend to error (doesn't accept empty string, use whitespace if you need)
|
||||
# php_value error_append_string " "
|
||||
|
||||
# Increase cookie security
|
||||
<IfModule mod_php5.c>
|
||||
php_value session.cookie_httponly true
|
||||
</IfModule>
|
||||
157
app/404.html
Normal file
157
app/404.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Page Not Found :(</title>
|
||||
<style>
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
html {
|
||||
padding: 30px 10px;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
color: #737373;
|
||||
background: #f0f0f0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
input {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 500px;
|
||||
_width: 500px;
|
||||
padding: 30px 20px 50px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 10px;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1.5em 0 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 380px;
|
||||
_width: 380px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* google search */
|
||||
|
||||
#goog-fixurl ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-fixurl form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-wm-qt,
|
||||
#goog-wm-sb {
|
||||
border: 1px solid #bbb;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
vertical-align: top;
|
||||
color: #444;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#goog-wm-qt {
|
||||
width: 220px;
|
||||
height: 20px;
|
||||
padding: 5px;
|
||||
margin: 5px 10px 0 0;
|
||||
box-shadow: inset 0 1px 1px #ccc;
|
||||
}
|
||||
|
||||
#goog-wm-sb {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
margin: 5px 0 0;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background-color: #f5f5f5;
|
||||
background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
*overflow: visible;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
#goog-wm-sb:hover,
|
||||
#goog-wm-sb:focus {
|
||||
border-color: #aaa;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
#goog-wm-qt:hover,
|
||||
#goog-wm-qt:focus {
|
||||
border-color: #105cb6;
|
||||
outline: 0;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
input::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Not found <span>:(</span></h1>
|
||||
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||
<p>It looks like this was the result of either:</p>
|
||||
<ul>
|
||||
<li>a mistyped address</li>
|
||||
<li>an out-of-date link</li>
|
||||
</ul>
|
||||
<script>
|
||||
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
|
||||
</script>
|
||||
<script src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
app/api/config.php
Normal file
20
app/api/config.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
$config = array(
|
||||
'server' => 'localhost',
|
||||
'db' => 'lentolaskuri2',
|
||||
'user' => 'lentolaskuri2',
|
||||
'password' => '8@89z~UIwavn',
|
||||
);
|
||||
|
||||
// ----
|
||||
|
||||
$mysqli = new mysqli($config['server'], $config['user'], $config['password'], $config['db']);
|
||||
|
||||
/* check connection */
|
||||
if (mysqli_connect_errno()) {
|
||||
printf("Connect failed: %s\n", mysqli_connect_error());
|
||||
exit();
|
||||
}
|
||||
28
app/api/import.php
Normal file
28
app/api/import.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require_once('config.php');
|
||||
|
||||
if (($handle = fopen("http://openflights.svn.sourceforge.net/viewvc/openflights/openflights/data/airports.dat", "r")) !== FALSE) {
|
||||
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
|
||||
$id = $mysqli->real_escape_string($data[0]);
|
||||
$name = $mysqli->real_escape_string($data[1]);
|
||||
$city = $mysqli->real_escape_string($data[2]);
|
||||
$country = $mysqli->real_escape_string($data[3]);
|
||||
$iata = $mysqli->real_escape_string($data[4]);
|
||||
$icao = $mysqli->real_escape_string($data[5]);
|
||||
if (empty($iata)) {
|
||||
continue;
|
||||
}
|
||||
$lat = $mysqli->real_escape_string($data[6]);
|
||||
$long = $mysqli->real_escape_string($data[7]);
|
||||
|
||||
$query = "REPLACE INTO airports (id, name, city, country, iata, icao, lat, `long`) VALUES ('$id', '$name', '$city', '$country', '$iata', '$icao', '$lat', '$long')";
|
||||
if (!$mysqli->query($query)) {
|
||||
printf("Error: %s\n", $mysqli->sqlstate);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
$mysqli->close();
|
||||
54
app/api/search.php
Normal file
54
app/api/search.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
require_once('config.php');
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$search = $mysqli->real_escape_string($_GET['s']);
|
||||
$id = $mysqli->real_escape_string($_GET['i']);
|
||||
if (empty($search) && empty($id)) {
|
||||
exit();
|
||||
}
|
||||
|
||||
$query = "";
|
||||
if (!empty($search)) {
|
||||
$query = "
|
||||
SELECT *,
|
||||
CASE
|
||||
WHEN iata LIKE '$search%' THEN 100
|
||||
WHEN name LIKE '$search%' THEN 75
|
||||
WHEN name LIKE '%$search%' THEN 74
|
||||
WHEN city LIKE '$search%' THEN 70
|
||||
WHEN country LIKE '$search%' THEN 65
|
||||
WHEN city LIKE '%$search%' THEN 60
|
||||
WHEN country LIKE '%$search%' THEN 55
|
||||
ELSE 0 END
|
||||
AS score
|
||||
FROM airports
|
||||
WHERE
|
||||
name LIKE '$search%' OR
|
||||
name LIKE '%$search%' OR
|
||||
country LIKE '$search%' OR
|
||||
country LIKE '%$search%' OR
|
||||
city LIKE '$search%' OR
|
||||
city LIKE '%$search%' OR
|
||||
iata LIKE '$search%'
|
||||
ORDER BY score DESC
|
||||
LIMIT 10;
|
||||
";
|
||||
} else if (!empty($id)) {
|
||||
$query ="SELECT * FROM airports WHERE id LIKE '$id';";
|
||||
}
|
||||
|
||||
$results = array();
|
||||
if ($result = $mysqli->query($query)) {
|
||||
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$results[] = $row;
|
||||
}
|
||||
|
||||
print(json_encode($results));
|
||||
|
||||
} else {
|
||||
printf("Error: %s\n", $mysqli->sqlstate);
|
||||
}
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
77
app/index.html
Normal file
77
app/index.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Lentolaskuri 2</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="styles/main.css">
|
||||
</head>
|
||||
<body data-views="calculator maps">
|
||||
|
||||
<div class="container calculator">
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<h1>Lentolaskuri2</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="span3 passengersWidget">
|
||||
<legend>
|
||||
Matkustajia
|
||||
</legend>
|
||||
<input type="number" placeholder="1" class="span3" />
|
||||
</div>
|
||||
<div class="span3 roundtripWidget">
|
||||
<!-- <legend>Meno-paluu</legend> -->
|
||||
<legend> </legend>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn active">Meno</button>
|
||||
<button type="button" class="btn roundtrip">Meno-paluu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<legend>Reitti</legend>
|
||||
<div class="route"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls row-fluid">
|
||||
<div class="span6 legInputWidget">
|
||||
<button class="btn btn-block"><span>Valitse lentokenttä</span> <i class="caret"></i></button>
|
||||
<input type="hidden"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span6 totalWidget"></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#map" data-toggle="tab">Kartta</a></li>
|
||||
<li><a href="#operation" data-toggle="tab">Laskurin toiminta</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="map">
|
||||
<div id="gmap" class="map"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="operation">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- build:js scripts/main.js -->
|
||||
<script data-main="scripts/main" src="components/requirejs/require.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
3
app/robots.txt
Normal file
3
app/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# robotstxt.org
|
||||
|
||||
User-agent: *
|
||||
40
app/scripts/app.js
Normal file
40
app/scripts/app.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'collections/route',
|
||||
'collections/results',
|
||||
'views/route',
|
||||
'views/leginput',
|
||||
'views/roundtrip',
|
||||
'views/passengers',
|
||||
'views/map',
|
||||
'views/total',
|
||||
'views/operation'
|
||||
], function (Route, Results, RouteView, LegInput, RoundtripInput, PassengersInput, MapView, TotalView, OperationView) {
|
||||
|
||||
var App = function () {
|
||||
this.initialize = function () {
|
||||
this.route = new Route();
|
||||
|
||||
this.legInput = new LegInput({collection: this.route});
|
||||
this.roundtripInput = new RoundtripInput();
|
||||
this.passengersInput = new PassengersInput();
|
||||
|
||||
this.routeView = new RouteView({collection: this.route});
|
||||
this.mapView = new MapView({collection: this.route});
|
||||
|
||||
// Order is important. ResultsView requires that Route legs are
|
||||
// on dom as ResultView references from and to legs to calculate
|
||||
// position.
|
||||
this.results = new Results([], {route: this.route});
|
||||
this.totalView = new TotalView({model: this.results.total});
|
||||
|
||||
new OperationView();
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
return App;
|
||||
|
||||
});
|
||||
53
app/scripts/collections/results.js
Normal file
53
app/scripts/collections/results.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'models/result',
|
||||
'models/total'
|
||||
], function (Backbone, Result, Total) {
|
||||
var Results = Backbone.Collection.extend({
|
||||
model: Result,
|
||||
initialize: function (model, opts) {
|
||||
this.route = opts.route;
|
||||
|
||||
this.route.on('add', this.newLeg, this);
|
||||
this.route.on('remove', this.calculate, this);
|
||||
this.route.on('sort', this.calculate, this);
|
||||
|
||||
this.total = new Total();
|
||||
},
|
||||
newLeg: function (model, collection, options) {
|
||||
if (this.route.length < 2) {
|
||||
return;
|
||||
}
|
||||
var from;
|
||||
if (this.length > 0) {
|
||||
from = this.route.get(this.last().get('to'));
|
||||
} else {
|
||||
from = this.route.first();
|
||||
}
|
||||
|
||||
var result = new Result({from: from, to: model});
|
||||
this.push(result);
|
||||
this.total.add(result);
|
||||
},
|
||||
calculate: function (model, collection, options) {
|
||||
var models = [];
|
||||
this.total.reset();
|
||||
|
||||
var prev = null;
|
||||
this.route.forEach(function (leg) {
|
||||
if (prev) {
|
||||
var result = new Result({from: prev, to: leg});
|
||||
models.push(result);
|
||||
this.total.add(result);
|
||||
}
|
||||
prev = leg;
|
||||
}.bind(this));
|
||||
|
||||
this.reset(models);
|
||||
}
|
||||
});
|
||||
|
||||
return Results;
|
||||
});
|
||||
19
app/scripts/collections/route.js
Normal file
19
app/scripts/collections/route.js
Normal file
@@ -0,0 +1,19 @@
|
||||
define([
|
||||
'lodash',
|
||||
'backbone',
|
||||
'../models/leg'
|
||||
], function (_, Backbone, Leg) {
|
||||
|
||||
// var stages = [];
|
||||
// var data = {};
|
||||
var num = 1;
|
||||
|
||||
var Route = Backbone.Collection.extend({
|
||||
model: Leg,
|
||||
comparator: function (model) {
|
||||
return model.get('order');
|
||||
}
|
||||
});
|
||||
|
||||
return Route;
|
||||
});
|
||||
64
app/scripts/config.js
Normal file
64
app/scripts/config.js
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
define(function () {
|
||||
return {
|
||||
api: 'http://work.lentolaskuri.fi/api',
|
||||
R: 6371,
|
||||
radiativeForceFactor: function (dist) {
|
||||
return (dist >= 500) ? 2.0 : 1.0;
|
||||
},
|
||||
distanceRanges: [{
|
||||
name: '0',
|
||||
}, {
|
||||
name: '500',
|
||||
min: 500
|
||||
}, {
|
||||
name: '1400',
|
||||
min: 1400
|
||||
}, {
|
||||
name: '5500',
|
||||
min: 5500,
|
||||
}, {
|
||||
name: '9000',
|
||||
min: 9000
|
||||
}],
|
||||
parameters: [{
|
||||
name: 'Suomi',
|
||||
regex: /^EF/,
|
||||
// Parameters for different distances
|
||||
co2factor: [0.08, 0.07, 0.07, 0.07, 0.07],
|
||||
ltoCycle: [15, 15, 15, 15, 15],
|
||||
load: [0.6, 0.65, 0.65, 0.65, 0.65],
|
||||
freight: [0.05, 0.05, 0.05, 0.05, 0.05]
|
||||
}, {
|
||||
name: 'Pohjois-Eurooppa',
|
||||
regex: /^E/,
|
||||
co2factor: [0.07, 0.06, 0.06, 0.06, 0.06],
|
||||
ltoCycle: [22, 22, 22, 22, 22],
|
||||
load: [0.65, 0.7, 0.75, 0.75, 0.75],
|
||||
freight: [0.05, 0.1, 0.1, 0.1, 0.1]
|
||||
}, {
|
||||
name: 'Etelä-Eurooppa',
|
||||
regex: /^L/,
|
||||
co2factor: [0.07, 0.07, 0.07, 0.07, 0.07],
|
||||
ltoCycle: [22, 22, 22, 22, 22],
|
||||
load: [0.65, 0.7, 0.8, 0.8, 0.8],
|
||||
freight: [0.05, 0.1, 0.1, 0.1, 0.1],
|
||||
}, {
|
||||
name: 'Euroopasta/aan',
|
||||
co2factor: [0.07, 0.07, 0.08, 0.09, 0.09],
|
||||
ltoCycle: [21, 24, 22, 26, 26], // 3rd value?
|
||||
load: [0.7, 0.7, 0.8, 0.8, 0.8],
|
||||
freight: [0.05, 0.15, 0.15, 0.15, 0.15]
|
||||
}, {
|
||||
name: 'Euroopan ulkopuoliset',
|
||||
co2factor: [0.08, 0.07, 0.08, 0.08, 0.09], // 2th value?
|
||||
ltoCycle: [21, 22, 23, 26, 26],
|
||||
load: [0.7, 0.7, 0.8, 0.8, 0.8],
|
||||
freight: [0.05, 0.1, 0.15, 0.2, 0.2]
|
||||
}],
|
||||
indirectRouteMultiplier: 1.09,
|
||||
// http://co2-raportti.fi/?heading=EU:n-p%C3%A4%C3%A4st%C3%B6kaupan-arvo-laski-kolmanneksen-t%C3%A4n%C3%A4-vuonna&page=ilmastouutisia&news_id=3665
|
||||
priceCO2: 7.5, // euros per tonne of CO2
|
||||
};
|
||||
});
|
||||
75
app/scripts/libs/airports.js
Normal file
75
app/scripts/libs/airports.js
Normal file
@@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
define([
|
||||
'jquery',
|
||||
'lodash',
|
||||
'config'
|
||||
], function ($, _, config) {
|
||||
var airportById = function (element, callback) {
|
||||
$.get('http://localhost:8000/search.php?i=' + element.val(), null, function (data) {
|
||||
callback(data[0]);
|
||||
});
|
||||
};
|
||||
|
||||
var data = function (term, page) {
|
||||
return {
|
||||
s: term
|
||||
};
|
||||
};
|
||||
|
||||
var results = function (data, page) {
|
||||
return {results: data};
|
||||
};
|
||||
|
||||
var findArea = function (icao) {
|
||||
for (var i = 0; i < config.parameters.length; ++i) {
|
||||
var param = config.parameters[i];
|
||||
if (param.regex && icao.match(param.regex)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return config.parameters.length - 2;
|
||||
};
|
||||
|
||||
var selectArea = function (fromCode, toCode) {
|
||||
var outside = config.parameters.length - 2;
|
||||
if (fromCode === outside && toCode === outside) {
|
||||
return config.parameters.length - 1;
|
||||
}
|
||||
return Math.max(fromCode, toCode);
|
||||
};
|
||||
|
||||
var selectRange = function (dist) {
|
||||
for (var i = 0; i < config.distanceRanges.length; ++i) {
|
||||
if (config.distanceRanges[i].min && dist <= config.distanceRanges[i].min) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Should select "greater of two", finland to western europe -> west europe
|
||||
// west europe to south europe -> south europe
|
||||
var parameters = function (from, to, range) {
|
||||
var i = selectArea(findArea(from.icao), findArea(to.icao));
|
||||
var p = config.parameters[i];
|
||||
return {
|
||||
name: p.name,
|
||||
co2factor: p.co2factor[range],
|
||||
ltoCycle: p.ltoCycle[range],
|
||||
load: p.load[range],
|
||||
freight: p.freight[range]
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
ajax: {
|
||||
url: config.api + '/search.php',
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
results: results
|
||||
},
|
||||
airportById: airportById,
|
||||
selectRange: selectRange,
|
||||
parameters: parameters
|
||||
};
|
||||
});
|
||||
9
app/scripts/libs/gmaps.js
Normal file
9
app/scripts/libs/gmaps.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
// convert Google Maps into an AMD module
|
||||
define([
|
||||
'async!http://maps.google.com/maps/api/js?v=3&sensor=false'
|
||||
], function() {
|
||||
// return the gmaps namespace for brevity
|
||||
return window.google.maps;
|
||||
});
|
||||
10
app/scripts/libs/handlebar_helpers.js
Normal file
10
app/scripts/libs/handlebar_helpers.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'handlebars'
|
||||
], function (Handlebars) {
|
||||
Handlebars.registerHelper('displayFloat', function (num, precision) {
|
||||
precision = precision;
|
||||
return ''+(num.toFixed(precision)).replace('.', ',');
|
||||
});
|
||||
});
|
||||
120
app/scripts/libs/maps.js
Normal file
120
app/scripts/libs/maps.js
Normal file
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'require',
|
||||
'jquery',
|
||||
'lodash',
|
||||
'backbone'
|
||||
], function (require, $, _, Backbone) {
|
||||
var gmaps;
|
||||
|
||||
function glatlng(latlng) {
|
||||
if (_.isArray(latlng)) {
|
||||
return new gmaps.LatLng(latlng[0], latlng[1]);
|
||||
} else if (latlng instanceof Backbone.Model) {
|
||||
return new gmaps.LatLng(latlng.get('lat'), latlng.get('long'));
|
||||
} else {
|
||||
return new gmaps.LatLng(latlng.lat, latlng.long);
|
||||
}
|
||||
}
|
||||
|
||||
var maps = {};
|
||||
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var i = 0;
|
||||
var Map = function (el, opts) {
|
||||
var num = i;
|
||||
deferred.done(function () {
|
||||
maps[num] = new gmaps.Map(el, {
|
||||
center: glatlng([0, 0]),
|
||||
mapTypeId: gmaps.MapTypeId.ROADMAP,
|
||||
zoom: 0,
|
||||
streetViewControl: false,
|
||||
mapTypeControl: false,
|
||||
draggable: false,
|
||||
scrollwheel: false,
|
||||
zoomControl: false
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
id: i++
|
||||
}
|
||||
};
|
||||
|
||||
var Line = function (attr) {
|
||||
var data;
|
||||
deferred.done(function () {
|
||||
attr = attr || {};
|
||||
attr.map = maps[attr.map.id];
|
||||
|
||||
data = new gmaps.Polyline(attr);
|
||||
});
|
||||
|
||||
return {
|
||||
clear: function () {
|
||||
deferred.done(function () {
|
||||
data.setPath([]);
|
||||
});
|
||||
},
|
||||
add: function (latlng) {
|
||||
deferred.done(function () {
|
||||
data.getPath().push(glatlng(latlng));
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var Bounds = function (map) {
|
||||
var map;
|
||||
var i;
|
||||
var data;
|
||||
|
||||
var reset = function () {
|
||||
i = 0;
|
||||
map.setZoom(1);
|
||||
map.setCenter(glatlng([0, 0]));
|
||||
};
|
||||
|
||||
deferred.done(function () {
|
||||
data = new gmaps.LatLngBounds();
|
||||
map = maps[map.id];
|
||||
|
||||
reset();
|
||||
});
|
||||
|
||||
return {
|
||||
clear: function () {
|
||||
deferred.done(function () {
|
||||
reset();
|
||||
});
|
||||
},
|
||||
add: function (latlng) {
|
||||
deferred.done(function () {
|
||||
data.extend(glatlng(latlng));
|
||||
++i;
|
||||
});
|
||||
},
|
||||
use: function () {
|
||||
deferred.done(function () {
|
||||
if (i >= 2) {
|
||||
map.fitBounds(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
require(['libs/gmaps'], function (_gmaps) {
|
||||
gmaps = _gmaps;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return {
|
||||
Map: Map,
|
||||
Line: Line,
|
||||
Bounds: Bounds
|
||||
};
|
||||
|
||||
});
|
||||
26
app/scripts/libs/math.js
Normal file
26
app/scripts/libs/math.js
Normal file
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
define([
|
||||
'config',
|
||||
'libs/airports'
|
||||
], function (cfg, airports) {
|
||||
var toRad = function toRad(n) {
|
||||
return n * Math.PI / 180;
|
||||
};
|
||||
|
||||
var haversine = function haversine(from, to) {
|
||||
var dLat = toRad(to.get('lat') - from.get('lat'));
|
||||
var dLon = toRad(to.get('long') - from.get('long'));
|
||||
var lat1 = toRad(from.get('lat'));
|
||||
var lat2 = toRad(to.get('lat'));
|
||||
|
||||
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
|
||||
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return cfg.R * c;
|
||||
};
|
||||
|
||||
return {
|
||||
toRad: toRad,
|
||||
haversine: haversine
|
||||
};
|
||||
});
|
||||
58
app/scripts/main.js
Normal file
58
app/scripts/main.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
require.config({
|
||||
map: {
|
||||
'*': {
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
jquery: '../components/jquery/jquery',
|
||||
bootstrap: '../scripts/bootstrap',
|
||||
select2: '../components/select2/select2',
|
||||
lodash: '../components/lodash/lodash',
|
||||
handlebars: '../components/handlebars/handlebars.runtime',
|
||||
Template: '../scripts/templates',
|
||||
'jquery-ui-core': '../components/jquery-ui/ui/jquery.ui.core',
|
||||
'jquery-ui-mouse': '../components/jquery-ui/ui/jquery.ui.mouse',
|
||||
'jquery-ui-widget': '../components/jquery-ui/ui/jquery.ui.widget',
|
||||
'jquery-ui-sortable': '../components/jquery-ui/ui/jquery.ui.sortable',
|
||||
async: '../components/requirejs-plugins/src/async',
|
||||
backbone: '../components/backbone/backbone',
|
||||
'backbone-mediator': '../components/Backbone-Mediator/backbone-mediator'
|
||||
},
|
||||
shim: {
|
||||
backbone: {
|
||||
deps: ['lodash'],
|
||||
exports: 'Backbone'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery'],
|
||||
exports: 'jquery'
|
||||
},
|
||||
select2: {
|
||||
deps: ['jquery'],
|
||||
exports: 'jquery'
|
||||
},
|
||||
'jquery-ui-core': {
|
||||
deps: ['jquery'],
|
||||
exports: 'jquery'
|
||||
},
|
||||
'jquery-ui-sortable': {
|
||||
deps: ['jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-mouse'],
|
||||
exports: 'jquery'
|
||||
},
|
||||
handlebars: {
|
||||
exports: 'Handlebars'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
require([
|
||||
'jquery',
|
||||
'app',
|
||||
'bootstrap',
|
||||
'libs/handlebar_helpers'
|
||||
], function ($, App) {
|
||||
var app = new App();
|
||||
});
|
||||
20
app/scripts/models/leg.js
Normal file
20
app/scripts/models/leg.js
Normal file
@@ -0,0 +1,20 @@
|
||||
define([
|
||||
'backbone'
|
||||
], function (Backbone) {
|
||||
|
||||
var num = 1;
|
||||
|
||||
var Leg = Backbone.Model.extend({
|
||||
initialize: function () {
|
||||
this.set('airportId', this.get('id'));
|
||||
this.set('id', num);
|
||||
++num;
|
||||
},
|
||||
destroy: function () {
|
||||
this.trigger('destroy', this, this.collection, {});
|
||||
}
|
||||
});
|
||||
|
||||
return Leg;
|
||||
|
||||
});
|
||||
45
app/scripts/models/result.js
Normal file
45
app/scripts/models/result.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'backbone-mediator',
|
||||
'libs/math',
|
||||
'libs/airports',
|
||||
'config'
|
||||
], function (Backbone, BackboneMediator, Math, airports, config) {
|
||||
var Result = Backbone.Model.extend({
|
||||
defaults: {
|
||||
dist: 0,
|
||||
total: 0,
|
||||
lto: 0,
|
||||
co2factor: 0,
|
||||
ltoCycle: 0,
|
||||
load: 0,
|
||||
freight: 0
|
||||
},
|
||||
initialize: function (attr, options) {
|
||||
if (this.get('from') && this.get('to')) {
|
||||
this.set('dist', Math.haversine(this.get('from'), this.get('to')));
|
||||
}
|
||||
|
||||
this.calculate.apply(this);
|
||||
},
|
||||
calculate: function () {
|
||||
var roundtrip = 0;
|
||||
if (this.get('from') && this.get('to')) {
|
||||
var roundtripFactor = (roundtrip) ? 2.0 : 1.0; // fuu
|
||||
|
||||
this.set('range', airports.selectRange(this.get('dist')));
|
||||
this.set(airports.parameters(this.get('from').toJSON(), this.get('to').toJSON(), this.get('range')));
|
||||
|
||||
var dist = this.get('dist') * config.indirectRouteMultiplier;
|
||||
|
||||
var m = config.radiativeForceFactor(dist) * (1 - this.get('freight')) * (1 / this.get('load'));
|
||||
this.set('lto', m * this.get('ltoCycle'));
|
||||
this.set('total', m * this.get('co2factor') * dist + this.get('lto'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Result;
|
||||
});
|
||||
54
app/scripts/models/total.js
Normal file
54
app/scripts/models/total.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'backbone-mediator',
|
||||
'libs/math',
|
||||
'libs/airports',
|
||||
'config'
|
||||
], function (Backbone, BackboneMediator, Math, airports, config) {
|
||||
var Result = Backbone.Model.extend({
|
||||
defaults: {
|
||||
dist: 0,
|
||||
total: 0,
|
||||
rawTotal: 0,
|
||||
alone: true,
|
||||
roundtrip: false,
|
||||
passengers: 1,
|
||||
price: 0
|
||||
},
|
||||
initialize: function () {
|
||||
Backbone.Mediator.subscribe('roundtrip:change', this.set.bind(this, 'roundtrip'));
|
||||
Backbone.Mediator.subscribe('passengers:change', this.set.bind(this, 'passengers'));
|
||||
Backbone.Mediator.subscribe('passengers:change', function (num) {
|
||||
this.set('alone', num === 1);
|
||||
}.bind(this));
|
||||
|
||||
this.on('change:roundtrip change:passengers', this.calc, this);
|
||||
},
|
||||
reset: function () {
|
||||
this.set('dist', 0);
|
||||
this.set('total', 0);
|
||||
this.set('rawTotal', 0);
|
||||
this.set('price', 0);
|
||||
},
|
||||
calc: function () {
|
||||
var mult = this.get('roundtrip') ? 2 : 1;
|
||||
this.set('total', mult * this.get('passengers') * this.get('rawTotal'));
|
||||
this.set('price', this.get('total') / 1000 * config.priceCO2);
|
||||
},
|
||||
add: function (another) {
|
||||
var keys = {
|
||||
'dist': 'dist',
|
||||
'total': 'rawTotal'
|
||||
};
|
||||
for (var i in keys) {
|
||||
this.set(keys[i], this.get(keys[i]) + another.get(i));
|
||||
}
|
||||
|
||||
this.calc();
|
||||
}
|
||||
});
|
||||
|
||||
return Result;
|
||||
});
|
||||
22
app/scripts/views/dropdown.js
Normal file
22
app/scripts/views/dropdown.js
Normal file
@@ -0,0 +1,22 @@
|
||||
define([
|
||||
'backbone',
|
||||
'select2'
|
||||
], function (Backbone) {
|
||||
var Dropdown = Backbone.View.extend({
|
||||
events: {
|
||||
'change': 'change'
|
||||
},
|
||||
initialize: function (opts) {
|
||||
this.$el.select2(opts.select2);
|
||||
},
|
||||
open: function () {
|
||||
this.$el.select2('open');
|
||||
},
|
||||
change: function () {
|
||||
this.trigger('new-leg', this.$el.select2('data'));
|
||||
this.$el.select2('data', {});
|
||||
}
|
||||
});
|
||||
|
||||
return Dropdown;
|
||||
});
|
||||
29
app/scripts/views/leg.js
Normal file
29
app/scripts/views/leg.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'Template'
|
||||
], function (Backbone, Template) {
|
||||
var LegView = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'leg',
|
||||
events: {
|
||||
'click a.delete': 'destroy'
|
||||
},
|
||||
initialize: function () {
|
||||
this.model.on('change', this.render, this);
|
||||
this.model.on('destroy', this.remove, this);
|
||||
},
|
||||
destroy: function () {
|
||||
this.model.destroy();
|
||||
},
|
||||
render: function () {
|
||||
this.$el.data('id', this.model.get('id'));
|
||||
this.$el.attr('id', 'leg-' + this.model.get('id')); // fuu
|
||||
this.$el.html(Template.leg(this.model.toJSON()));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
return LegView;
|
||||
});
|
||||
39
app/scripts/views/leginput.js
Normal file
39
app/scripts/views/leginput.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'models/leg',
|
||||
'views/dropdown',
|
||||
'libs/airports',
|
||||
'Template'
|
||||
], function (Backbone, Leg, Dropdown, airports, Template) {
|
||||
var LegInput = Backbone.View.extend({
|
||||
el: '.legInputWidget',
|
||||
events: {
|
||||
'click button': 'openDropdown'
|
||||
},
|
||||
initialize: function () {
|
||||
this.dropdown = new Dropdown({
|
||||
el: this.$el.find('input'),
|
||||
select2: {
|
||||
initSelection: airports.airportById,
|
||||
formatResult: Template.choice,
|
||||
formatSelection: Template.choice,
|
||||
minimumInputLength: 1,
|
||||
ajax: airports.ajax
|
||||
}
|
||||
});
|
||||
|
||||
this.dropdown.bind('new-leg', this.newLeg, this);
|
||||
},
|
||||
openDropdown: function () {
|
||||
this.dropdown.open();
|
||||
},
|
||||
newLeg: function (data) {
|
||||
this.collection.push(new Leg(data));
|
||||
}
|
||||
});
|
||||
|
||||
return LegInput;
|
||||
});
|
||||
|
||||
41
app/scripts/views/map.js
Normal file
41
app/scripts/views/map.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'libs/maps'
|
||||
], function (Backbone, Maps) {
|
||||
|
||||
var MapView = Backbone.View.extend({
|
||||
el: '#gmap',
|
||||
initialize: function () {
|
||||
this.collection.on('sort', this.render, this);
|
||||
this.collection.on('remove', this.render, this);
|
||||
this.collection.on('add', this.add, this);
|
||||
|
||||
this.map = new Maps.Map(this.el);
|
||||
|
||||
this.route = new Maps.Line({
|
||||
geodesic: true,
|
||||
map: this.map,
|
||||
strokeColor: '#000'
|
||||
});
|
||||
|
||||
this.bounds = new Maps.Bounds(this.map);
|
||||
},
|
||||
render: function () {
|
||||
this.route.clear();
|
||||
this.bounds.clear();
|
||||
this.collection.forEach(this.route.add);
|
||||
this.collection.forEach(this.bounds.add);
|
||||
this.bounds.use();
|
||||
},
|
||||
add: function (model, collection, options) {
|
||||
this.route.add(model);
|
||||
this.bounds.add(model);
|
||||
this.bounds.use();
|
||||
}
|
||||
});
|
||||
|
||||
return MapView;
|
||||
|
||||
});
|
||||
22
app/scripts/views/operation.js
Normal file
22
app/scripts/views/operation.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'Template',
|
||||
'config',
|
||||
'bootstrap'
|
||||
], function (Backbone, Template, config) {
|
||||
var Operation = Backbone.View.extend({
|
||||
el: '#operation',
|
||||
initialize: function () {
|
||||
this.render.apply(this);
|
||||
},
|
||||
render: function () {
|
||||
this.$el.html(Template.operation(config));
|
||||
this.$el.find('abbr').tooltip();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
return Operation;
|
||||
});
|
||||
28
app/scripts/views/passengers.js
Normal file
28
app/scripts/views/passengers.js
Normal file
@@ -0,0 +1,28 @@
|
||||
define([
|
||||
'backbone',
|
||||
'backbone-mediator'
|
||||
], function (Backbone) {
|
||||
var PassengersInput = Backbone.View.extend({
|
||||
el: '.passengersWidget',
|
||||
events: {
|
||||
'change input': 'change',
|
||||
'keyup input': 'change'
|
||||
},
|
||||
initialize: function () {
|
||||
this.value = 1;
|
||||
},
|
||||
change: function (event) {
|
||||
var val = this.$el.find('input').val();
|
||||
if (!val) {
|
||||
val = 1;
|
||||
}
|
||||
val = Number(val);
|
||||
if (val !== this.value) {
|
||||
this.value = val;
|
||||
Backbone.Mediator.publish('passengers:change', this.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return PassengersInput;
|
||||
});
|
||||
22
app/scripts/views/roundtrip.js
Normal file
22
app/scripts/views/roundtrip.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'backbone-mediator'
|
||||
], function (Backbone) {
|
||||
var RoundtripInput = Backbone.View.extend({
|
||||
el: '.roundtripWidget',
|
||||
events: {
|
||||
'click button': 'click'
|
||||
},
|
||||
click: function (event, el) {
|
||||
this.$el.find('button').removeClass('active');
|
||||
var current = this.$el.find(event.currentTarget);
|
||||
current.addClass('active');
|
||||
|
||||
Backbone.Mediator.publish('roundtrip:change', current.hasClass('roundtrip'));
|
||||
}
|
||||
});
|
||||
|
||||
return RoundtripInput;
|
||||
});
|
||||
49
app/scripts/views/route.js
Normal file
49
app/scripts/views/route.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'views/leg',
|
||||
'jquery-ui-sortable'
|
||||
], function (Backbone, LegView) {
|
||||
|
||||
var RouteView = Backbone.View.extend({
|
||||
el: '.route',
|
||||
events: {
|
||||
'sortstart': 'sortStart',
|
||||
'sortstop': 'sortStop'
|
||||
},
|
||||
initialize: function () {
|
||||
this.collection.bind('add', this.add, this);
|
||||
this.collection.bind('sort', this.render, this);
|
||||
|
||||
this.$el.sortable({
|
||||
handle: 'div > div',
|
||||
scroll: false,
|
||||
});
|
||||
},
|
||||
sortStart: function () {
|
||||
// fadeOut results
|
||||
},
|
||||
sortStop: function (event, ui) {
|
||||
this.updateSort();
|
||||
// fadeIn results
|
||||
},
|
||||
updateSort: function () {
|
||||
// jQuery UI already updates DOM order,
|
||||
// so we only have to update Backbone collection to match that
|
||||
var self = this;
|
||||
this.$el.find('.leg').each(function (index) {
|
||||
// Get id of model from element
|
||||
var id = $(this).data('id');
|
||||
self.collection.get(id).set('order', index);
|
||||
});
|
||||
this.collection.sort();
|
||||
},
|
||||
add: function (model, collection, options) {
|
||||
var item = new LegView({model: model});
|
||||
this.$el.append(item.render().el);
|
||||
}
|
||||
});
|
||||
|
||||
return RouteView;
|
||||
});
|
||||
19
app/scripts/views/total.js
Normal file
19
app/scripts/views/total.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'Template'
|
||||
], function (Backbone, Template) {
|
||||
var TotalView = Backbone.View.extend({
|
||||
el: '.totalWidget',
|
||||
initialize: function () {
|
||||
this.render.apply(this);
|
||||
this.model.bind('change', this.render, this);
|
||||
},
|
||||
render: function () {
|
||||
this.$el.html(Template.total(this.model.toJSON()));
|
||||
}
|
||||
});
|
||||
|
||||
return TotalView;
|
||||
});
|
||||
141
app/styles/main.less
Normal file
141
app/styles/main.less
Normal file
@@ -0,0 +1,141 @@
|
||||
@import 'bootstrap/less/bootstrap';
|
||||
@import 'font-awesome/build/assets/font-awesome/less/font-awesome';
|
||||
@import (less) 'select2/select2.css';
|
||||
|
||||
.calculator {
|
||||
.inactive {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.nav-tabs {
|
||||
a {
|
||||
font-size: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
display: block;
|
||||
.select2-choice {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-result {
|
||||
abbr {
|
||||
display: block;
|
||||
width: 3em;
|
||||
float: left;
|
||||
color: #1657A1;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
border-top: 1px solid #aaa;
|
||||
.select2-search input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
&.select2-drop-above {
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.leg {
|
||||
text-align: left;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
padding: 10px 40px 10px 0;
|
||||
margin: 0 0;
|
||||
& > div {
|
||||
& > div {
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
i.icon-sort, .delete {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -7px;
|
||||
float: right;
|
||||
}
|
||||
i.icon-sort {
|
||||
left: 0;
|
||||
}
|
||||
.delete {
|
||||
right: 0;
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
abbr {
|
||||
float: right;
|
||||
font-weight: normal;
|
||||
}
|
||||
i {
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
.route {
|
||||
position: relative;
|
||||
.leg {
|
||||
position: relative;
|
||||
left: 0 !important;
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roundtripWidget {
|
||||
.btn-group {
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.resultsWidget {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.legInputWidget {
|
||||
.btn {
|
||||
text-align: left;
|
||||
span {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.totalWidget {
|
||||
margin: 20px 0 0 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
.distance {
|
||||
display: block;
|
||||
}
|
||||
.raw {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.co2 {
|
||||
font-size: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 300px;
|
||||
}
|
||||
1
app/templates/choice.hbs
Normal file
1
app/templates/choice.hbs
Normal file
@@ -0,0 +1 @@
|
||||
{{name}}<abbr>{{codes}}</abbr>
|
||||
13
app/templates/leg.hbs
Normal file
13
app/templates/leg.hbs
Normal file
@@ -0,0 +1,13 @@
|
||||
<div>
|
||||
<div>
|
||||
<i class="icon-sort"></i>
|
||||
<span class="">
|
||||
<h2>{{name}}</h2>
|
||||
<span class="city">{{city}}, </span>
|
||||
<span class="country">{{country}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<a href="#" class="btn btn-danger delete">
|
||||
<i class="icon-remove"></i>
|
||||
</a>
|
||||
</div>
|
||||
58
app/templates/operation.hbs
Normal file
58
app/templates/operation.hbs
Normal file
@@ -0,0 +1,58 @@
|
||||
<p>
|
||||
Laskuri noutaa tietokannasta, jonka lähteenä on <a href="http://openflights.org/">http://openflights.org/</a>, lentokenttien sijainnit.
|
||||
Etäisyys kahden koordinaatin välillä lasketaan <a href="http://en.wikipedia.org/wiki/Haversine_formula">Haversinen kaavalla</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Lähtö- ja kohdekentälle valitaan lentoalue niiden <abbr title="International Civil Aviation Organization">ICAO</abbr>-tunnusten perusteella.
|
||||
Mikäli toinen kenttä on esimerkiksi Suomessa ja toinen Pohjois-Euroopassa, valitaan muuttujat jälkimmäisen mukaan.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Säteilypakotekerroin</strong> <strong>1,0</strong> tai matkan ollessa yli 500km <strong>2,0</strong>.</li>
|
||||
<li><strong>Epäsuoramatkakerroin</strong> – koska reitit eivät kulje suorinta mahdollista reittiä, kerrotaan matka kertoimella <strong>{{displayFloat indirectRouteMultiplier 2}}</strong>.
|
||||
<li><strong>CO<sub>2</sub> päästötonnin hinta</strong> {{displayFloat priceCO2 2}}€</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Muuttujista rakennetaan kerroin <em>m</em> seuraavasti <em>säteilypakotekerroin * (1 - rahtikerroin) * (1 / kuormakerroin)</em>.
|
||||
Lopulliset co<sub>2</sub> päästöt koostuvat kahdesta osasta: laskeutuminen ja nousu sekä lento. Lennon
|
||||
päästöt lasketaan kertomalla kuljettu etäisyys kertoimilla <em>m</em> ja <em>co2</em>. Laskeutumisen ja nousut päästöt saadaan kertomalla <em>lto</em>-muuttuja kertoimella <em>m</em>.
|
||||
</p>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="2"><strong>Etäisyys [km]</strong></i></td>
|
||||
{{#each distanceRanges}}
|
||||
<td><strong>{{name}}</strong></td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Lentoalue</strong></td>
|
||||
<td><strong>Muuttuja</strong></td>
|
||||
<td colspan="5"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{{#each parameters}}
|
||||
<tr>
|
||||
<td rowspan="4">{{name}}</td>
|
||||
<td>co<sub>2</sub></td>
|
||||
{{#each co2factor}}<td>{{displayFloat this 2}}</td>{{/each}}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>lto</td>
|
||||
{{#each ltoCycle}}<td>{{this}}</td>{{/each}}
|
||||
</tr>
|
||||
<tr>
|
||||
<td><abbr title="Paljonko lennoilla on matkustajia.">kuorma</abbr></td>
|
||||
{{#each load}}<td>{{displayFloat this 2}}</td>{{/each}}
|
||||
</tr>
|
||||
<tr>
|
||||
<td><abbr title="Paljonko koneet ottavat rahtia.">rahti</abbr></td>
|
||||
{{#each freight}}<td>{{displayFloat this 2}}</td>{{/each}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
</table>
|
||||
1
app/templates/result.hbs
Normal file
1
app/templates/result.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<span class="distance">{{displayFloat dist 2}} km</span><span class="co2"><abbr title="lto: {{displayFloat lto 2}}">{{displayFloat total 2}} Kg CO²</abbr></span>
|
||||
15
app/templates/total.hbs
Normal file
15
app/templates/total.hbs
Normal file
@@ -0,0 +1,15 @@
|
||||
<p class="distance">Matkaa yhteensä {{displayFloat dist 1}} <em>km</em>.</p>
|
||||
|
||||
<p class="raw">{{#if alone}}
|
||||
{{#if roundtrip}}
|
||||
Päästöjä per suunta {{displayFloat rawTotal 0}} <em>Kg CO<sub>2</sub></em>.
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if roundtrip}}
|
||||
Päästöjä per henkilö per suunta {{displayFloat rawTotal 0}} <em>Kg CO²</em>.
|
||||
{{else}}
|
||||
Päästöjä per henkilö {{displayFloat rawTotal 0}} <em>Kg CO<sub>2</sub></em>.
|
||||
{{/if}}
|
||||
{{/if}}</p>
|
||||
<p class="co2">{{displayFloat total 0}} <em>kg CO<sub>2</sub></em></p>
|
||||
<p class="price">{{displayFloat price 2}} <em>€</em></p>
|
||||
18
component.json
Normal file
18
component.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "lentolaskuri2",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"backbone": "git://github.com/documentcloud/backbone.git#1.0.0",
|
||||
"bootstrap": "git://github.com/twitter/bootstrap.git#2.3.1",
|
||||
"requirejs": "git://github.com/jrburke/requirejs#2.1.5",
|
||||
"jquery": "git://github.com/components/jquery.git#1.9.1",
|
||||
"select2": "git://github.com/ivaynberg/select2.git#~3.3.1",
|
||||
"lodash": "git://github.com/bestiejs/lodash.git#1.0.1",
|
||||
"font-awesome": "~3.1.1",
|
||||
"handlebars": "git://github.com/components/handlebars.js#1.0.0-rc.3",
|
||||
"jquery-ui": "git://github.com/components/jqueryui#1.10.1",
|
||||
"requirejs-plugins": "git://github.com/millermedeiros/requirejs-plugins.git",
|
||||
"Backbone-Mediator": "latest",
|
||||
"i18next": "~1.6.0"
|
||||
}
|
||||
}
|
||||
18
lentolaskuri.sublime-project
Normal file
18
lentolaskuri.sublime-project
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"path": "/home/juho/Source/tyo/lentolaskuri2",
|
||||
"folder_exclude_patterns": ["node_modules", ".cache", ".tmp", "app/components"]
|
||||
}
|
||||
],
|
||||
"ternjs": {
|
||||
"exclude": ["node_modules/**", "app/components/**"],
|
||||
"libs": ["jquery"],
|
||||
"plugins": {
|
||||
"requirejs": {
|
||||
"baseURL": "app/components"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "lentolaskuri2",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-contrib-copy": "~0.4.0",
|
||||
"grunt-contrib-concat": "~0.1.2",
|
||||
"grunt-contrib-coffee": "~0.4.0",
|
||||
"grunt-contrib-uglify": "~0.1.1",
|
||||
"grunt-contrib-less": "~0.5.0",
|
||||
"grunt-contrib-jshint": "~0.1.1",
|
||||
"grunt-contrib-cssmin": "~0.4.1",
|
||||
"grunt-contrib-connect": "0.1.2",
|
||||
"grunt-contrib-clean": "0.4.0",
|
||||
"grunt-contrib-htmlmin": "0.1.1",
|
||||
"grunt-contrib-imagemin": "0.1.2",
|
||||
"grunt-contrib-livereload": "0.1.1",
|
||||
"grunt-bower-hooks": "~0.2.0",
|
||||
"grunt-usemin": "~0.1.9",
|
||||
"grunt-regarde": "~0.1.1",
|
||||
"grunt-requirejs": "~0.3.1",
|
||||
"grunt-mocha": "~0.2.2",
|
||||
"grunt-open": "~0.2.0",
|
||||
"matchdep": "~0.1.1",
|
||||
"grunt-contrib-handlebars": "~0.5.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user