├── client ├── src │ ├── models │ │ └── .gitkeep │ ├── services │ │ ├── logger.js │ │ ├── config.js │ │ ├── session.js │ │ └── geo-google.js │ ├── app.html │ ├── components │ │ ├── map-google │ │ │ ├── map-google.js │ │ │ └── map-google.html │ │ ├── layout-admin │ │ │ ├── layout-admin.js │ │ │ ├── footer-main │ │ │ │ ├── footer-main.html │ │ │ │ └── footer-main.js │ │ │ ├── style │ │ │ │ ├── labels.less │ │ │ │ ├── AdminLTE.less │ │ │ │ ├── skin-purple.less │ │ │ │ ├── print.less │ │ │ │ ├── forms.less │ │ │ │ ├── progress-bars.less │ │ │ │ ├── core.less │ │ │ │ ├── buttons.less │ │ │ │ ├── navs.less │ │ │ │ ├── variables.less │ │ │ │ └── miscellaneous.less │ │ │ ├── header-main │ │ │ │ ├── header-main.js │ │ │ │ └── header-main.less │ │ │ ├── layout-admin.html │ │ │ ├── side-bar-left │ │ │ │ ├── side-bar-left.html │ │ │ │ ├── side-bar-left.js │ │ │ │ └── side-bar-left.less │ │ │ └── side-bar-right │ │ │ │ ├── side-bar-right.js │ │ │ │ ├── side-bar-right.less │ │ │ │ └── side-bar-right.html │ │ └── wizard-form │ │ │ ├── wizard-form.less │ │ │ ├── wizard-form.js │ │ │ └── wizard-form.html │ ├── routes │ │ ├── geo │ │ │ ├── geo.less │ │ │ ├── place-card │ │ │ │ ├── place-card.less │ │ │ │ ├── place-card.js │ │ │ │ └── place-card.html │ │ │ ├── index.js │ │ │ └── index.html │ │ ├── vendors │ │ │ ├── services │ │ │ │ ├── service-list.js │ │ │ │ ├── service-add-terms.html │ │ │ │ ├── service-add-basic-info.html │ │ │ │ ├── service-add.html │ │ │ │ ├── service-add-prices.html │ │ │ │ ├── service-list.html │ │ │ │ └── service-add.js │ │ │ ├── vendors.less │ │ │ ├── index.js │ │ │ ├── index.html │ │ │ ├── new.js │ │ │ ├── photo-sphere.js │ │ │ ├── new.html │ │ │ ├── photo-sphere.html │ │ │ ├── vendor.js │ │ │ └── vendor.html │ │ ├── home │ │ │ ├── index.js │ │ │ └── index.html │ │ └── log │ │ │ ├── post.js │ │ │ ├── index.html │ │ │ ├── post.html │ │ │ ├── index.js │ │ │ ├── new.js │ │ │ └── new.html │ ├── utils │ │ ├── converters │ │ │ ├── uppercase.js │ │ │ └── newLineToBreak.js │ │ ├── index.js │ │ ├── to-geo-json.js │ │ └── layout │ │ │ └── config.js │ ├── main.js │ ├── app.js │ └── app.less ├── .jshintrc ├── .npmignore ├── doc │ └── api.json ├── favicon.ico ├── img │ ├── avatar.png │ ├── icons.png │ ├── photo1.png │ ├── photo2.png │ ├── avatar04.png │ ├── avatar2.png │ ├── avatar3.png │ ├── avatar5.png │ ├── boxed-bg.jpg │ ├── boxed-bg.png │ ├── credit │ │ ├── visa.png │ │ ├── cirrus.png │ │ ├── mestro.png │ │ ├── paypal.png │ │ ├── paypal2.png │ │ ├── mastercard.png │ │ └── american-express.png │ ├── mgen-mohawk.png │ ├── default-50x50.gif │ ├── user1-128x128.jpg │ ├── user2-160x160.jpg │ ├── user3-128x128.jpg │ ├── user4-128x128.jpg │ ├── user5-128x128.jpg │ ├── user6-128x128.jpg │ ├── user7-128x128.jpg │ └── user8-128x128.jpg ├── .gitignore ├── gulpfile.js ├── build │ ├── babel-options.js │ ├── tasks │ │ ├── clean.js │ │ ├── lint.js │ │ ├── serve.js │ │ ├── doc.js │ │ ├── test.js │ │ ├── dev.js │ │ ├── watch.js │ │ ├── e2e.js │ │ ├── prepare-release.js │ │ ├── less.js │ │ └── build.js │ ├── args.js │ └── paths.js ├── .editorconfig ├── test │ ├── e2e │ │ ├── src │ │ │ ├── skeleton.po.js │ │ │ ├── welcome.po.js │ │ │ └── demo.spec.js │ │ └── dist │ │ │ ├── skeleton.po.js │ │ │ ├── demo.spec.js │ │ │ └── welcome.po.js │ └── unit │ │ ├── flickr.spec.js │ │ ├── app.spec.js │ │ └── child-router.spec.js ├── CONTRIBUTING.md ├── protractor.conf.js ├── LICENSE ├── index.html ├── aurelia.protractor.js ├── karma.conf.js ├── package.json ├── README.md ├── images │ └── icons │ │ └── Alien.svg └── vendor │ └── marker-clusterer │ └── marker-clusterer.js ├── server ├── .eslintignore ├── config │ ├── auth-exempt.js │ ├── auth-check.js │ ├── authenticate.js │ ├── environment.js │ └── koa.js ├── controllers │ ├── user.js │ ├── post.js │ └── auth.js ├── .editorconfig ├── config.js ├── run.js ├── server.js ├── models │ ├── album.js │ ├── post.js │ ├── status.js │ ├── trip.js │ ├── event.js │ ├── photo.js │ ├── user.js │ ├── vendor.js │ └── service.js ├── .eslintrc ├── package.json └── .gitignore ├── package.json ├── .jsbeautifyrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── .eslintrc └── README.md /client/src/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/services/logger.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /client/.npmignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | bower_components 3 | .idea -------------------------------------------------------------------------------- /client/doc/api.json: -------------------------------------------------------------------------------- 1 | {"classes":[],"methods":[],"properties":[],"events":[]} -------------------------------------------------------------------------------- /server/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*/api/blueprints 2 | **/*/api/services/Ember.js 3 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/favicon.ico -------------------------------------------------------------------------------- /client/src/app.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /client/src/components/map-google/map-google.js: -------------------------------------------------------------------------------- 1 | export class MapGoogle { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/avatar.png -------------------------------------------------------------------------------- /client/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/icons.png -------------------------------------------------------------------------------- /client/img/photo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/photo1.png -------------------------------------------------------------------------------- /client/img/photo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/photo2.png -------------------------------------------------------------------------------- /client/src/components/layout-admin/layout-admin.js: -------------------------------------------------------------------------------- 1 | export class LayoutAdmin { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "babel-eslint": "^3.1.17" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/img/avatar04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/avatar04.png -------------------------------------------------------------------------------- /client/img/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/avatar2.png -------------------------------------------------------------------------------- /client/img/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/avatar3.png -------------------------------------------------------------------------------- /client/img/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/avatar5.png -------------------------------------------------------------------------------- /client/img/boxed-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/boxed-bg.jpg -------------------------------------------------------------------------------- /client/img/boxed-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/boxed-bg.png -------------------------------------------------------------------------------- /client/src/routes/geo/geo.less: -------------------------------------------------------------------------------- 1 | .geo #mapfeed { 2 | width: 100%; 3 | height: 300px; 4 | } 5 | -------------------------------------------------------------------------------- /client/img/credit/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/visa.png -------------------------------------------------------------------------------- /client/img/mgen-mohawk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/mgen-mohawk.png -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | jspm_packages 3 | bower_components 4 | .idea 5 | .DS_STORE 6 | /dist 7 | -------------------------------------------------------------------------------- /client/img/credit/cirrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/cirrus.png -------------------------------------------------------------------------------- /client/img/credit/mestro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/mestro.png -------------------------------------------------------------------------------- /client/img/credit/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/paypal.png -------------------------------------------------------------------------------- /client/img/default-50x50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/default-50x50.gif -------------------------------------------------------------------------------- /client/img/user1-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user1-128x128.jpg -------------------------------------------------------------------------------- /client/img/user2-160x160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user2-160x160.jpg -------------------------------------------------------------------------------- /client/img/user3-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user3-128x128.jpg -------------------------------------------------------------------------------- /client/img/user4-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user4-128x128.jpg -------------------------------------------------------------------------------- /client/img/user5-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user5-128x128.jpg -------------------------------------------------------------------------------- /client/img/user6-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user6-128x128.jpg -------------------------------------------------------------------------------- /client/img/user7-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user7-128x128.jpg -------------------------------------------------------------------------------- /client/img/user8-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/user8-128x128.jpg -------------------------------------------------------------------------------- /client/img/credit/paypal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/paypal2.png -------------------------------------------------------------------------------- /client/src/components/map-google/map-google.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /client/img/credit/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/mastercard.png -------------------------------------------------------------------------------- /server/config/auth-exempt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | auth: ['login', 'register'], 3 | user: ['custom'] 4 | } 5 | -------------------------------------------------------------------------------- /client/img/credit/american-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgenev/nautilus/HEAD/client/img/credit/american-express.png -------------------------------------------------------------------------------- /server/controllers/user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | * get_custom (next) { 3 | yield next; 4 | this.body = 'custom user return'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/src/utils/converters/uppercase.js: -------------------------------------------------------------------------------- 1 | export class UppercaseValueConverter { 2 | toView(value){ 3 | return value && value.toUpperCase(); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/gulpfile.js: -------------------------------------------------------------------------------- 1 | // all gulp tasks are located in the ./build/tasks directory 2 | // gulp configuration is in files in ./build directory 3 | require('require-dir')('build/tasks'); 4 | -------------------------------------------------------------------------------- /client/src/utils/converters/newLineToBreak.js: -------------------------------------------------------------------------------- 1 | export class newLineToBreakValueConverter { 2 | toView(value){ 3 | return value.replace(/(?:\r\n|\r|\n)/g, '
'); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function configure(aurelia) { 2 | aurelia.globalizeResources('./converters/uppercase'); 3 | aurelia.globalizeResources('./converters/newLineToBreak'); 4 | } 5 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-list.js: -------------------------------------------------------------------------------- 1 | import {customElement, bindable} from 'aurelia-framework'; 2 | 3 | @customElement('service-list') 4 | export class SayHello { 5 | @bindable services = []; 6 | } 7 | -------------------------------------------------------------------------------- /server/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /client/src/services/config.js: -------------------------------------------------------------------------------- 1 | class Config { 2 | server = { 3 | apiPrefix: '/api/', 4 | address: 'http://localhost:3000', 5 | url: 'http://localhost:3000/api/' 6 | } 7 | } 8 | 9 | export { 10 | Config 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/services/session.js: -------------------------------------------------------------------------------- 1 | class Session { 2 | currentUser = { 3 | email: "user@user.com", 4 | firstName: "joe", 5 | lastName: "baboon", 6 | _id: "5573a830d1fe9fab9b421ffb" 7 | } 8 | } 9 | 10 | export { 11 | Session 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/utils/to-geo-json.js: -------------------------------------------------------------------------------- 1 | export default { 2 | point: point 3 | }; 4 | 5 | export function point (latlong, properties) { 6 | return { 7 | "type": "Point", 8 | "coordinates": [latlong.lng, latlong.lat] 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-add-terms.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_size": 2, 3 | "indent_char": " ", 4 | "other": " ", 5 | "indent_level": 0, 6 | "indent_with_tabs": false, 7 | "preserve_newlines": true, 8 | "max_preserve_newlines": 2, 9 | "jslint_happy": true, 10 | "indent_handlebars": true 11 | } 12 | -------------------------------------------------------------------------------- /server/config/auth-check.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return function* (next) { 3 | if (this.user.userid > 0) { 4 | yield next; 5 | } else { 6 | this.status = 401; 7 | this.body = 'Must be logged in to see this!'; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/build/babel-options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | modules: 'system', 3 | moduleIds: false, 4 | comments: false, 5 | compact: false, 6 | stage:2, 7 | optional: [ 8 | "runtime", 9 | "es7.decorators", 10 | "es7.classProperties", 11 | "es7.asyncFunctions" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /client/src/routes/vendors/vendors.less: -------------------------------------------------------------------------------- 1 | .vendor { 2 | #mapfeed { 3 | width: 100%; 4 | height: 300px; 5 | } 6 | 7 | // .modal-dialog { 8 | // width: 100%; 9 | // padding: 0; 10 | // } 11 | // 12 | // .modal-content { 13 | // height: 100%; 14 | // border-radius: 0; 15 | // } 16 | } 17 | -------------------------------------------------------------------------------- /client/build/tasks/clean.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var del = require('del'); 4 | var vinylPaths = require('vinyl-paths'); 5 | 6 | // deletes all files in the output path 7 | gulp.task('clean', function() { 8 | return gulp.src([paths.output]) 9 | .pipe(vinylPaths(del)); 10 | }); 11 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /client/test/e2e/src/skeleton.po.js: -------------------------------------------------------------------------------- 1 | export class PageObject_Skeleton { 2 | 3 | constructor() { 4 | 5 | } 6 | 7 | getCurrentPageTitle() { 8 | return browser.getTitle(); 9 | } 10 | 11 | navigateTo(href) { 12 | element(by.css('a[href="' + href + '"]')).click(); 13 | return browser.waitForHttpDone(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/footer-main/footer-main.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | export function configure(aurelia) { 2 | aurelia.use 3 | .standardConfiguration() 4 | .developmentLogging() 5 | .plugin('./utils/index') 6 | .plugin('aurelia-bs-modal') 7 | .plugin('aurelia-computed'); 8 | // .plugin('aurelia-animator-css'); 9 | 10 | 11 | aurelia.start().then(a => a.setRoot()); 12 | } 13 | -------------------------------------------------------------------------------- /client/build/tasks/lint.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var jshint = require('gulp-jshint'); 4 | var stylish = require('jshint-stylish'); 5 | 6 | // runs jshint on all .js files 7 | gulp.task('lint', function() { 8 | return gulp.src(paths.source) 9 | .pipe(jshint()) 10 | .pipe(jshint.reporter(stylish)); 11 | }); 12 | -------------------------------------------------------------------------------- /client/src/routes/home/index.js: -------------------------------------------------------------------------------- 1 | import {computedFrom} from 'aurelia-framework'; 2 | 3 | export class HomeIndexRoute { 4 | heading = 'Welcome to the Aurelia Navigation App!'; 5 | firstName = 'John'; 6 | lastName = 'Doe'; 7 | 8 | @computedFrom('firstName', 'lastName') 9 | get fullName(){ 10 | return `${this.firstName} ${this.lastName}`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/build/args.js: -------------------------------------------------------------------------------- 1 | var yargs = require('yargs'); 2 | 3 | var argv = yargs.argv, 4 | validBumpTypes = "major|minor|patch|prerelease".split("|"), 5 | bump = (argv.bump || 'patch').toLowerCase(); 6 | 7 | if(validBumpTypes.indexOf(bump) === -1) { 8 | throw new Error('Unrecognized bump "' + bump + '".'); 9 | } 10 | 11 | module.exports = { 12 | bump: bump 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | es5_server/* 3 | !es5_server/package.json 4 | 5 | # Ignore configuration and automatically generated cruft 6 | dbconfig.php 7 | sitemap.xml 8 | *.log 9 | .sass-cache 10 | .settings 11 | # Operating system files 12 | .Spotlight-V100 13 | .Trashes 14 | .DS_Store 15 | .DS_Store? 16 | ehthumbs.db 17 | Thumbs.db 18 | 19 | #webstorm/phpstorm files 20 | .idea 21 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | "baseURL": "/", 3 | "transpiler": "traceur", 4 | "paths": { 5 | "*": "*.js", 6 | "github:*": "jspm_packages/github/*.js" 7 | } 8 | }); 9 | 10 | System.config({ 11 | "map": { 12 | "traceur": "github:jmcriffey/bower-traceur@0.0.88", 13 | "traceur-runtime": "github:jmcriffey/bower-traceur-runtime@0.0.88" 14 | } 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /server/run.js: -------------------------------------------------------------------------------- 1 | require('babel/register'); 2 | var app = require('./server'); 3 | 4 | 5 | // auto init if this app is not being initialized by another module (i.e. using require('./app').init();) 6 | // if (!module.parent) { 7 | // try { 8 | // app.init(); 9 | // } 10 | // catch (err) { 11 | // console.error(err); 12 | // process.exit(1); 13 | // } 14 | // } 15 | app.init(); 16 | -------------------------------------------------------------------------------- /client/src/routes/geo/place-card/place-card.less: -------------------------------------------------------------------------------- 1 | .place-card { 2 | margin-top:5px; 3 | min-height:160px; 4 | } 5 | 6 | place-card { 7 | background-color: red; 8 | } 9 | .list-group-item.active small { 10 | color: #fff; 11 | } 12 | 13 | .stars { 14 | margin: 20px auto 1px; 15 | } 16 | 17 | @media screen and (max-width: 300px) { 18 | body { 19 | background-color: lightblue; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | services: 7 | - mongodb 8 | 9 | sudo: false 10 | 11 | cache: 12 | directories: 13 | - node_modules 14 | 15 | before_install: 16 | - "npm config set spin false" 17 | - "npm install -g npm@^2" 18 | - "npm install -g bower" 19 | 20 | env: 21 | - TEST_DIR=client 22 | - TEST_DIR=server 23 | 24 | script: 25 | - cd $TEST_DIR && npm install && bower install && npm test 26 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/labels.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Label 3 | * ---------------- 4 | */ 5 | .label-default { 6 | background-color: @gray; 7 | color: #444; 8 | } 9 | .label-danger { 10 | &:extend(.bg-red); 11 | } 12 | .label-info { 13 | &:extend(.bg-aqua); 14 | } 15 | .label-waring { 16 | &:extend(.bg-yellow); 17 | } 18 | .label-primary { 19 | &:extend(.bg-light-blue); 20 | } 21 | .label-success { 22 | &:extend(.bg-green); 23 | } -------------------------------------------------------------------------------- /client/build/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var appRoot = 'src/'; 4 | var outputRoot = 'dist/'; 5 | 6 | module.exports = { 7 | root: appRoot, 8 | source: appRoot + '**/*.js', 9 | less: appRoot + '**/*.less', 10 | html: appRoot + '**/*.html', 11 | styleEntry: appRoot + 'app.less', 12 | output: outputRoot, 13 | sourceMapRelativePath: '../' + appRoot, 14 | doc:'./doc', 15 | e2eSpecsSrc: 'test/e2e/src/*.js', 16 | e2eSpecsDist: 'test/e2e/dist/' 17 | }; 18 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/header-main/header-main.js: -------------------------------------------------------------------------------- 1 | export class HeaderMain { 2 | attached() { 3 | let o = $.AdminLTE.options; 4 | //Add slimscroll to navbar dropdown 5 | if (o.navbarMenuSlimscroll && typeof $.fn.slimScroll != 'undefined') { 6 | 7 | $(".navbar .menu").slimScroll({ 8 | height: o.navbarMenuHeight, 9 | alwaysVisible: false, 10 | size: o.navbarMenuSlimscrollWidth 11 | }).css("width", "100%"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/components/wizard-form/wizard-form.less: -------------------------------------------------------------------------------- 1 | .wizard .wizard-active { 2 | background-color: #008cba; 3 | color: white; 4 | } 5 | 6 | .wizard { 7 | background-color: white; 8 | margin-bottom: 25px; 9 | } 10 | 11 | .wizard-form { 12 | padding-left: 20px; 13 | padding-right: 20px; 14 | } 15 | 16 | 17 | .wizard-wrapper { 18 | background-color: white; 19 | padding:15px; 20 | } 21 | 22 | .wizard-step { 23 | background-color: white; 24 | padding: 20px; 25 | cursor: pointer; 26 | } 27 | -------------------------------------------------------------------------------- /client/build/tasks/serve.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserSync = require('browser-sync'); 3 | 4 | // this task utilizes the browsersync plugin 5 | // to create a dev server instance 6 | // at http://localhost:9000 7 | gulp.task('serve', ['build'], function(done) { 8 | browserSync({ 9 | open: false, 10 | port: 9000, 11 | server: { 12 | baseDir: ['.'], 13 | middleware: function (req, res, next) { 14 | res.setHeader('Access-Control-Allow-Origin', '*'); 15 | next(); 16 | } 17 | } 18 | }, done); 19 | }); 20 | -------------------------------------------------------------------------------- /server/controllers/post.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | let post = mongoose.model('post'); 3 | 4 | module.exports = { 5 | init(app) { 6 | app.get('/api/posts/test/:id', test); 7 | }, 8 | * get_custom(next) { 9 | yield next; 10 | this.body = 'custom post return'; 11 | } 12 | } 13 | 14 | let test = function* (next) { 15 | yield next; 16 | try { 17 | let result = yield post.findById(this.params.id).exec(); 18 | return this.body = result; 19 | } catch (err) { 20 | this.status = 404; 21 | return this.body = err; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import config from './config/environment'; 2 | import koaConfig from './config/koa'; 3 | import co from 'co'; 4 | import koa from 'koa'; 5 | import mongoose from 'mongoose'; 6 | 7 | let app = koa(); 8 | module.exports = app; 9 | 10 | app.init = () => { 11 | // connect to mongo 12 | mongoose.connect('mongodb://localhost/nautilus'); 13 | // set up koa 14 | koaConfig(app); 15 | // lift server 16 | app.server = app.listen(config.app.port); 17 | if (config.app.env !== 'test') { 18 | console.log('Koa server up on port ' + config.app.port); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /server/models/album.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let album = new Schema({ 7 | user: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | location: GeoJSON.Point, 13 | address:String, 14 | name: String, 15 | description:String, 16 | tags : [String] 17 | }); 18 | 19 | album.plugin(SimpleTimestamps); 20 | album.plugin(autopopulate); 21 | 22 | module.exports = album; 23 | -------------------------------------------------------------------------------- /server/models/post.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 4 | import permalink from 'mongoose-permalink'; 5 | 6 | let post = new Schema({ 7 | title: String, 8 | content: String, 9 | user: { 10 | type: Schema.ObjectId, 11 | ref: 'user', 12 | autopopulate: true 13 | }, 14 | tags: Array 15 | }); 16 | 17 | post.plugin(permalink, { 18 | sources: ['title'] 19 | }); 20 | post.plugin(SimpleTimestamps); 21 | post.plugin(autopopulate); 22 | 23 | module.exports = post; 24 | -------------------------------------------------------------------------------- /server/models/status.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let status = new Schema({ 7 | user: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | note:String, 13 | state:String, 14 | activity: String, 15 | location: GeoJSON.Point, 16 | address:String 17 | }); 18 | 19 | status.plugin(SimpleTimestamps); 20 | status.plugin(autopopulate); 21 | 22 | module.exports = status; 23 | -------------------------------------------------------------------------------- /client/build/tasks/doc.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var tools = require('aurelia-tools'); 3 | var paths = require('../paths'); 4 | var yuidoc = require('gulp-yuidoc'); 5 | 6 | // uses yui to generate documentation to doc/api.json 7 | gulp.task('doc-generate', function(){ 8 | return gulp.src(paths.source) 9 | .pipe(yuidoc.parser(null, 'api.json')) 10 | .pipe(gulp.dest(paths.doc)); 11 | }); 12 | 13 | // takes output of doc-generate task 14 | // and cleans it up for use with aurelia 15 | // documentation app 16 | gulp.task('doc', ['doc-generate'], function(){ 17 | tools.transformAPIModel(paths.doc); 18 | }); 19 | -------------------------------------------------------------------------------- /client/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute and to make this project even better than it is today! If this interests you, please begin by reading [our contributing guidelines](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md). The contributing document will provide you with all the information you need to get started. Once you have read that, you will need to also [sign our CLA](http://goo.gl/forms/dI8QDDSyKR) before we can accept a Pull Request from you. More information on the process is included in the [contributor's guide](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md). 4 | -------------------------------------------------------------------------------- /client/build/tasks/test.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var karma = require('karma').server; 3 | 4 | /** 5 | * Run test once and exit 6 | */ 7 | gulp.task('test', function (done) { 8 | karma.start({ 9 | configFile: __dirname + '/../../karma.conf.js', 10 | singleRun: true 11 | }, function(e) { 12 | done(); 13 | }); 14 | }); 15 | 16 | /** 17 | * Watch for file changes and re-run tests on each change 18 | */ 19 | gulp.task('tdd', function (done) { 20 | karma.start({ 21 | configFile: __dirname + '/../../karma.conf.js' 22 | }, function(e) { 23 | done(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /server/models/trip.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let trip = new Schema({ 7 | user: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | name:String, 13 | description:String, 14 | startDate: Date, 15 | endDate: Date, 16 | locations: [GeoJSON.Point], 17 | tags: [String], 18 | photos: [{ type: Schema.Types.ObjectId, ref: 'photo' }] 19 | }); 20 | 21 | trip.plugin(SimpleTimestamps); 22 | trip.plugin(autopopulate); 23 | 24 | module.exports = trip; 25 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-add-basic-info.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/layout-admin.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /client/src/components/wizard-form/wizard-form.js: -------------------------------------------------------------------------------- 1 | import {customElement, computedFrom, bindable} from 'aurelia-framework'; 2 | 3 | @customElement('wizard-form') 4 | export class WizardForm { 5 | @bindable steps = []; 6 | @bindable model = null; 7 | activeStep = 0; 8 | 9 | @computedFrom('activeStep') 10 | get firstStep() { 11 | return this.activeStep === 0; 12 | } 13 | 14 | @computedFrom('activeStep', 'steps') 15 | get lastStep() { 16 | return this.activeStep === this.steps.length - 1; 17 | } 18 | 19 | nextStep() { 20 | ++this.activeStep; 21 | } 22 | 23 | prevStep() { 24 | --this.activeStep; 25 | } 26 | 27 | goToStep(step) { 28 | this.activeStep = step; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/models/event.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let event = new Schema({ 7 | organizer: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | guests: [{ 13 | type: Schema.ObjectId, 14 | ref: 'user', 15 | autopopulate: true 16 | }], 17 | location: GeoJSON.Point, 18 | address:String, 19 | title: String, 20 | description: String, 21 | eventDate: Date 22 | }); 23 | 24 | event.plugin(SimpleTimestamps); 25 | event.plugin(autopopulate); 26 | 27 | module.exports = event; 28 | -------------------------------------------------------------------------------- /client/build/tasks/dev.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var tools = require('aurelia-tools'); 3 | 4 | // source code for the tasks called in this file 5 | // is located at: https://github.com/aurelia/tools/blob/master/src/dev.js 6 | 7 | // updates dependencies in this folder 8 | // from folders in the parent directory 9 | gulp.task('update-own-deps', function(){ 10 | tools.updateOwnDependenciesFromLocalRepositories(); 11 | }); 12 | 13 | // quickly pulls in all of the aurelia 14 | // github repos, placing them up one directory 15 | // from where the command is executed, 16 | // then runs `npm install` 17 | // and `gulp build` for each repo 18 | gulp.task('build-dev-env', function () { 19 | tools.buildDevEnv(); 20 | }); 21 | -------------------------------------------------------------------------------- /server/config/authenticate.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | module.exports = function* (next) { 3 | let authHeader, token, elements, scheme; 4 | authHeader = this.get('Authorization'); 5 | 6 | if (authHeader) { 7 | elements = authHeader.split(' '); 8 | if (elements.length === 2) { 9 | scheme = elements[0]; 10 | if (scheme === 'Bearer') { 11 | token = elements[1]; 12 | try { 13 | console.log('YES TOKEN'); 14 | this.user = jwt.verify(token, secret); 15 | } catch (err) { 16 | console.log('NO TOKEN'); 17 | } 18 | } 19 | } 20 | } 21 | this.user = this.user || {}; 22 | yield next; 23 | } 24 | -------------------------------------------------------------------------------- /server/models/photo.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let photo = new Schema({ 7 | user: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | album: { 13 | type: Schema.ObjectId, 14 | ref: 'album' 15 | }, 16 | location: GeoJSON.Point, 17 | address:String, 18 | name: String, 19 | filePath: String, 20 | thumbPath: String, 21 | description:String, 22 | tags : [String] 23 | }); 24 | 25 | photo.plugin(SimpleTimestamps); 26 | photo.plugin(autopopulate); 27 | 28 | module.exports = photo; 29 | -------------------------------------------------------------------------------- /client/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // An example configuration file. 2 | exports.config = { 3 | directConnect: true, 4 | 5 | // Capabilities to be passed to the webdriver instance. 6 | capabilities: { 7 | 'browserName': 'chrome' 8 | }, 9 | 10 | //seleniumAddress: 'http://0.0.0.0:4444', 11 | // add proper version number 12 | seleniumServerJar: './node_modules/gulp-protractor/node_modules/protractor/selenium/selenium-server-standalone-2.44.0.jar', 13 | specs: ['specs/e2e/dist/*.js'], 14 | 15 | plugins: [{ 16 | path: 'aurelia.protractor.js' 17 | }], 18 | 19 | 20 | // Options to be passed to Jasmine-node. 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 3 | import mongooseHidden from 'mongoose-hidden'; 4 | import mongooseBcrypt from 'mongoose-bcrypt'; 5 | 6 | let user = new Schema({ 7 | email: String, 8 | firstName: String, 9 | lastName: String, 10 | password: { type: String, required: true, bcrypt: true }, 11 | address: String, 12 | website: String, 13 | tagline: String, 14 | zipcode: Number, 15 | userType: Number 16 | }); 17 | 18 | user.plugin(mongooseBcrypt); 19 | user.plugin(SimpleTimestamps); 20 | user.methods.toJSON = function() { 21 | var obj = this.toObject() 22 | delete obj.password; 23 | return obj; 24 | }; 25 | 26 | module.exports = user; 27 | -------------------------------------------------------------------------------- /server/models/vendor.js: -------------------------------------------------------------------------------- 1 | import {Schema} from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import GeoJSON from 'mongoose-geojson-schema'; 4 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 5 | 6 | let vendor = new Schema({ 7 | user: { 8 | type: Schema.ObjectId, 9 | ref: 'user', 10 | autopopulate: true 11 | }, 12 | name: String, 13 | location: GeoJSON.Point, 14 | description: String, 15 | address: String, 16 | urlSegment: String, 17 | category: String, 18 | logo: String, 19 | photos: [{ 20 | type: Schema.ObjectId, 21 | ref: 'photo', 22 | autopopulate: true 23 | }], 24 | }); 25 | 26 | vendor.plugin(SimpleTimestamps); 27 | vendor.plugin(autopopulate); 28 | 29 | module.exports = vendor; 30 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-add.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /client/src/routes/log/post.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | 5 | @inject(HttpClient, Config) 6 | export class LogPostRoute { 7 | heading = 'Single Post'; 8 | endPoint = 'posts'; 9 | 10 | constructor(http, config){ 11 | this.http = http; 12 | this.config = config; 13 | } 14 | 15 | async activate(params, routeConfig) { 16 | try { 17 | let post = await this.http.get(`${this.config.server.url}${this.endPoint}/${params.id}`); 18 | this.post = post.content; 19 | routeConfig.navModel.title = this.post.title; 20 | } catch (err) { 21 | // TODO flash a global error message 22 | console.log('error connecting: ', err); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/routes/log/index.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /client/src/routes/geo/place-card/place-card.js: -------------------------------------------------------------------------------- 1 | import {customElement, bindable, computedFrom, inject} from 'aurelia-framework'; 2 | import {GeoGoogleService} from 'services/geo-google'; 3 | 4 | @customElement('place-card') 5 | @inject(GeoGoogleService) 6 | export class PlaceCard { 7 | @bindable place = null; 8 | @bindable class = null; 9 | @bindable index = null; 10 | 11 | constructor(geo) { 12 | this.geo = geo; 13 | } 14 | 15 | @computedFrom('place') 16 | get photoUrl() { 17 | if (this.place && this.place.photos) { 18 | return this.place.photos[0].getUrl({'maxWidth': 100, 'maxHeight': 100}); 19 | } else if (this.place) { 20 | return this.place.icon; 21 | } 22 | } 23 | 24 | showMarkerInfoWindow(i) { 25 | this.geo.placeListingClick(window.markers[i]); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /client/src/routes/log/post.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /server/models/service.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import autopopulate from 'mongoose-autopopulate'; 3 | import {SimpleTimestamps} from 'mongoose-SimpleTimestamps'; 4 | 5 | let Schema = mongoose.Schema; 6 | 7 | let service = new mongoose.Schema({ 8 | vendor: { 9 | type: Schema.ObjectId, 10 | ref: 'vendor', 11 | autopopulate: true 12 | }, 13 | name:String, 14 | description: String, 15 | terms: String, 16 | priceAdult: Number, 17 | priceChild: Number, 18 | deposit: Number, // percentage stored as decimal 19 | category: { type: String, enum: ['activity', 'lodging', 'food', 'education'] }, 20 | minCapacity: Number, 21 | maxCapacity: Number 22 | // TODO hours, dates available etc. possibly locations 23 | }); 24 | 25 | service.plugin(autopopulate); 26 | 27 | module.exports = service; 28 | -------------------------------------------------------------------------------- /client/src/components/wizard-form/wizard-form.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /client/src/routes/log/index.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | import {Session} from 'services/session'; 5 | 6 | @inject(HttpClient, Config, Session) 7 | export class LogIndexRoute { 8 | heading = 'Posts'; 9 | posts = []; 10 | endPoint = 'posts'; 11 | 12 | constructor(http, config, session){ 13 | this.http = http; 14 | this.config = config; 15 | this.session = session; 16 | } 17 | 18 | async activate() { 19 | try { 20 | let query = `{"user": "${this.session.currentUser._id}"}`; 21 | let posts = await this.http.get(`${this.config.server.url}${this.endPoint}?conditions=${query}`); 22 | this.posts = posts.content; 23 | } catch (err) { 24 | // TODO flash a global error message 25 | console.log('error connecting: ', err); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/build/tasks/watch.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var browserSync = require('browser-sync'); 4 | // outputs changes to files to the console 5 | function reportChange(event){ 6 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 7 | } 8 | 9 | // this task wil watch for changes 10 | // to js, html, and css files and call the 11 | // reportChange method. Also, by depending on the 12 | // serve task, it will instantiate a browserSync session 13 | gulp.task('watch', ['serve'], function() { 14 | gulp.watch(paths.source, ['build-system', browserSync.reload]).on('change', reportChange); 15 | gulp.watch(paths.html, ['build-html', browserSync.reload]).on('change', reportChange); 16 | gulp.watch(paths.less, ['less', browserSync.reload]).on('change', reportChange); 17 | gulp.watch(paths.style, browserSync.reload).on('change', reportChange); 18 | }); 19 | -------------------------------------------------------------------------------- /client/src/routes/vendors/index.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | import {Session} from 'services/session'; 5 | 6 | @inject(HttpClient, Config, Session) 7 | export class VendorsIndexRoute { 8 | heading = 'Vendors'; 9 | vendors = []; 10 | endPoint = 'vendors'; 11 | 12 | constructor(http, config, session){ 13 | this.http = http; 14 | this.config = config; 15 | this.session = session; 16 | } 17 | 18 | async activate() { 19 | try { 20 | let query = `{"user": "${this.session.currentUser._id}"}`; 21 | let vendors = await this.http.get(`${this.config.server.url}${this.endPoint}?conditions=${query}`); 22 | this.vendors = vendors.content; 23 | } catch (err) { 24 | // TODO flash a global error message 25 | console.log('error connecting: ', err); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/routes/vendors/index.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /client/test/unit/flickr.spec.js: -------------------------------------------------------------------------------- 1 | import {Flickr} from '../../src/flickr'; 2 | 3 | class HttpStub { 4 | jsonp(url) { 5 | var response = this.itemStub; 6 | this.url = url; 7 | return new Promise((resolve) => { 8 | resolve({ content: { items: response } }); 9 | }) 10 | } 11 | } 12 | 13 | describe('the Flickr module', () => { 14 | 15 | it('sets jsonp response to images', (done) => { 16 | var http = new HttpStub(), 17 | sut = new Flickr(http), 18 | itemStubs = [1], 19 | itemFake = [2]; 20 | 21 | http.itemStub = itemStubs; 22 | sut.activate().then(() => { 23 | expect(sut.images).toBe(itemStubs); 24 | expect(sut.images).not.toBe(itemFake); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('calls confirm on canDeactivate', () => { 30 | var sut = new Flickr(), 31 | global = jasmine.getGlobal(); 32 | spyOn(global, "confirm"); 33 | sut.canDeactivate(); 34 | expect(global.confirm).toHaveBeenCalled(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/AdminLTE.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * AdminLTE v2.1.0 3 | * Author: Almsaeed Studio 4 | * Website: Almsaeed Studio 5 | * License: Open source - MIT 6 | * Please visit http://opensource.org/licenses/MIT for more information 7 | !*/ 8 | 9 | //google fonts 10 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic); 11 | 12 | //Bootstrap Variables & Mixins 13 | //The core bootstrap code have not been modified. These files 14 | //are included only for reference. 15 | 16 | //MISC 17 | //---- 18 | @import "core.less"; 19 | @import "variables.less"; 20 | @import "mixins.less"; 21 | 22 | //COMPONENTS 23 | //----------- 24 | @import "dropdown.less"; 25 | @import "forms.less"; 26 | @import "buttons.less"; 27 | @import "navs.less"; 28 | @import "labels.less"; 29 | @import "progress-bars.less"; 30 | 31 | //Miscellaneous 32 | //------------- 33 | @import "miscellaneous.less"; 34 | @import "print.less"; 35 | -------------------------------------------------------------------------------- /client/test/e2e/src/welcome.po.js: -------------------------------------------------------------------------------- 1 | export class PageObject_Welcome { 2 | 3 | constructor() { 4 | 5 | } 6 | 7 | getGreeting() { 8 | return element(by.tagName('h2')).getText(); 9 | } 10 | 11 | setFirstname(value) { 12 | return element(by.valueBind('firstName')).clear().sendKeys(value); 13 | } 14 | 15 | setLastname(value) { 16 | return element(by.valueBind('lastName')).clear().sendKeys(value); 17 | } 18 | 19 | getFullname() { 20 | return element(by.css('.help-block')).getText(); 21 | } 22 | 23 | pressSubmitButton() { 24 | return element(by.css('button[type="submit"]')).click(); 25 | } 26 | 27 | openAlertDialog() { 28 | return browser.wait(() => { 29 | this.pressSubmitButton(); 30 | 31 | return browser.switchTo().alert().then( 32 | // use alert.accept instead of alert.dismiss which results in a browser crash 33 | function(alert) { alert.accept(); return true; }, 34 | function() { return false; } 35 | ); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/test/unit/app.spec.js: -------------------------------------------------------------------------------- 1 | import {App} from '../../src/app'; 2 | 3 | class RouterStub { 4 | configure(handler) { 5 | handler(this); 6 | } 7 | map(routes) { 8 | this.routes = routes; 9 | } 10 | } 11 | 12 | describe('the App module', () => { 13 | var sut; 14 | beforeEach(() => { sut = new App(new RouterStub()); }); 15 | 16 | it('contains a router property', () => { 17 | expect(sut.router).toBeDefined(); 18 | }); 19 | 20 | it('configures the router title', () => { 21 | expect(sut.router.title).toEqual('Aurelia'); 22 | }); 23 | 24 | it('should have a welcome route', () => { 25 | expect(sut.router.routes).toContain({ route: ['','welcome'], moduleId: './welcome', nav: true, title:'Welcome' }); 26 | }); 27 | 28 | it('should have a flickr route', () => { 29 | expect(sut.router.routes).toContain({ route: 'flickr', moduleId: './flickr', nav: true }); 30 | }); 31 | 32 | it('should have a child router route', () => { 33 | expect(sut.router.routes).toContain({ route: 'child-router', moduleId: './child-router', nav: true, title:'Child Router' }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /server/controllers/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import config from '../config/environment'; 3 | import mongoose from 'mongoose'; 4 | let User = mongoose.model('user'); 5 | 6 | module.exports = { 7 | * post_login (next) { 8 | yield next; 9 | let credentials = this.request.body; 10 | let user = yield User.findOne({email: credentials.email}, {email: 1, name: 1, password: 1}); 11 | let valid = user.verifyPasswordSync(credentials.password); 12 | 13 | if (!user) { 14 | this.throw(401, 'Incorrect e-mail address.'); 15 | } else if (!valid) { 16 | this.throw(401, 'Incorrect password.'); 17 | } 18 | // sign and send the token along with the user info 19 | let token = jwt.sign(user, config.app.secret, {expiresInMinutes: 90 * 24 * 60 /* 90 days */}); 20 | this.body = {token,user}; 21 | }, 22 | * post_register (next) { 23 | yield next; 24 | try { 25 | let result = yield User.create(this.request.body); 26 | this.status = 201; 27 | return this.body = result; 28 | } catch (err) { 29 | return this.body = err; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/routes/geo/place-card/place-card.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-add-prices.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /client/src/app.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap'; 2 | import 'utils/layout/slimscroll'; 3 | import 'jquery'; 4 | 5 | export class App { 6 | configureRouter(config, router){ 7 | config.title = 'Nautilus'; 8 | config.map([ 9 | { route: ['', 'home'], moduleId: './routes/home/index', nav: true, title:'Welcome' }, 10 | { route: 'geo-demo', moduleId: './routes/geo/index', nav: true, title: 'Geo Demo' }, 11 | { route: 'log', moduleId: './routes/log/index', name: 'posts', nav: true, title: 'Posts' }, 12 | { route: 'log/post/:id', moduleId: './routes/log/post', name: 'postById'}, 13 | { route: 'log/new', moduleId: './routes/log/new', nav: true, title: 'New Post' }, 14 | { route: 'vendors', moduleId: './routes/vendors/index', name: 'vendors', nav: true, title: 'Vendors' }, 15 | { route: 'vendors/:id', moduleId: './routes/vendors/vendor', name: 'vendorById'}, 16 | { route: 'vendors/new', moduleId: './routes/vendors/new', nav: true, title: 'New Vendor' } 17 | ]); 18 | 19 | this.router = router; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /client/test/unit/child-router.spec.js: -------------------------------------------------------------------------------- 1 | import {ChildRouter} from '../../src/child-router'; 2 | 3 | class RouterStub { 4 | configure(handler) { 5 | handler(this); 6 | } 7 | map(routes) { 8 | this.routes = routes; 9 | } 10 | } 11 | 12 | describe('the Child Router module', () => { 13 | var sut; 14 | beforeEach(() => { sut = new ChildRouter(new RouterStub()); }); 15 | 16 | it('contains a router property', () => { 17 | expect(sut.router).toBeDefined(); 18 | }); 19 | 20 | it('configures the heading', () => { 21 | expect(sut.heading).toEqual('Child Router'); 22 | }); 23 | 24 | it('should have a welcome route', () => { 25 | expect(sut.router.routes).toContain({ route: ['','welcome'], moduleId: './welcome', nav: true, title:'Welcome' }); 26 | }); 27 | 28 | it('should have a flickr route', () => { 29 | expect(sut.router.routes).toContain({ route: 'flickr', moduleId: './flickr', nav: true }); 30 | }); 31 | 32 | it('should have a child router route', () => { 33 | expect(sut.router.routes).toContain({ route: 'child-router', moduleId: './child-router', nav: true, title:'Child Router' }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Durandal Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/skin-purple.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Skin: Purple 3 | * ------------ 4 | */ 5 | 6 | .skin-purple-light { 7 | //Navbar 8 | .main-header { 9 | .navbar { 10 | .navbar-variant(@purple; #fff); 11 | .sidebar-toggle { 12 | color: #fff; 13 | &:hover { 14 | background-color: darken(@purple, 5%); 15 | } 16 | } 17 | @media(max-width: @screen-header-collapse) { 18 | .dropdown-menu { 19 | li { 20 | &.divider { 21 | background-color: rgba(255,255,255,0.1); 22 | } 23 | a { 24 | color: #fff; 25 | &:hover { 26 | background: darken(@purple, 5%); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | //Logo 34 | .logo { 35 | .logo-variant(@purple); 36 | } 37 | 38 | li.user-header { 39 | background-color: @purple; 40 | } 41 | } 42 | 43 | //Content Header 44 | .content-header { 45 | background: transparent; 46 | } 47 | 48 | //Create the sidebar skin 49 | .skin-light-sidebar(@purple); 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Genev, Gemini Connect LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/src/routes/log/new.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from '../../services/config'; 4 | import {Session} from '../../services/session'; 5 | import {computedFrom} from 'aurelia-framework'; 6 | import {Router} from 'aurelia-router'; 7 | 8 | @inject(HttpClient, Config, Router, Session) 9 | export class LogNewRoute { 10 | heading = 'Posts'; 11 | endPoint = 'posts'; 12 | 13 | @computedFrom('title', 'content') 14 | get post(){ 15 | return { title: this.title, content: this.content, user: this.session.currentUser._id }; 16 | } 17 | 18 | constructor(http, config, router, session){ 19 | this.http = http; 20 | this.config = config; 21 | this.router = router; 22 | this.session = session; 23 | } 24 | 25 | async createPost() { 26 | try { 27 | let newPost = await this.http.post(`${this.config.server.url}${this.endPoint}`, this.post); 28 | this.router.navigateToRoute('postById', {id: newPost.content._id}); 29 | } catch (err) { 30 | // TODO flash a global error message 31 | console.log('error connecting: ', err); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/print.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Misc: print 3 | * ----------- 4 | */ 5 | @media print { 6 | //Add to elements that you do not want to show when printing 7 | .no-print { 8 | display: none!important; 9 | } 10 | //Elements that we want to hide when printing 11 | .main-sidebar, 12 | .left-side, 13 | .main-header, 14 | .content-header { 15 | &:extend(.no-print); 16 | } 17 | //This is the only element that should appear, so let's remove the margins 18 | .content-wrapper, 19 | .right-side, 20 | .main-footer { 21 | margin-left: 0!important; 22 | min-height: 0!important; 23 | .translate(0,0)!important; 24 | } 25 | .fixed .content-wrapper, 26 | .fixed .right-side { 27 | padding-top: 0!important; 28 | } 29 | //Invoice printing 30 | .invoice { 31 | width: 100%; 32 | border: 0; 33 | margin: 0; 34 | padding: 0; 35 | } 36 | .invoice-col { 37 | float: left; 38 | width: 33.3333333%; 39 | } 40 | //Make sure table content displays properly 41 | .table-responsive { 42 | overflow: auto; 43 | > .table tr th, 44 | > .table tr td { 45 | white-space: normal!important; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /client/src/app.less: -------------------------------------------------------------------------------- 1 | @import "bootstrap/bootstrap"; 2 | @import "bootstrap/mixins"; 3 | @import "bootstrap/variables"; 4 | @import "components/layout-admin/style/AdminLTE.less"; 5 | @import "components/layout-admin/style/skin-purple.less"; 6 | @import "components/layout-admin/header-main/header-main.less"; 7 | @import "components/layout-admin/side-bar-left/side-bar-left.less"; 8 | @import "components/layout-admin/side-bar-right/side-bar-right.less"; 9 | @import "components/wizard-form/wizard-form.less"; 10 | @import "routes/vendors/vendors.less"; 11 | @import "routes/geo/geo.less"; 12 | @import "routes/geo/place-card/place-card.less"; 13 | 14 | .space-top { 15 | margin-top: 10px; 16 | } 17 | 18 | .space-bottom { 19 | margin-bottom: 10px; 20 | } 21 | 22 | .splash { 23 | text-align: center; 24 | margin: 10% 0 0; 25 | box-sizing: border-box; 26 | } 27 | 28 | .splash .message { 29 | font-size: 54px; 30 | line-height: 54px; 31 | text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px; 32 | text-transform: uppercase; 33 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 34 | } 35 | 36 | .splash .fa-spinner { 37 | text-align: center; 38 | display: inline-block; 39 | font-size: 54px; 40 | margin-top: 50px; 41 | } 42 | -------------------------------------------------------------------------------- /client/src/routes/vendors/new.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'aurelia-router'; 2 | import {inject} from 'aurelia-framework'; 3 | import {HttpClient} from 'aurelia-http-client'; 4 | import {Config} from 'services/config'; 5 | import {Session} from 'services/session'; 6 | import {GeoGoogleService} from 'services/geo-google'; 7 | 8 | @inject(HttpClient, Config, Router, Session, GeoGoogleService) 9 | export class VendorNewRoute { 10 | heading = 'Create Vendor'; 11 | endPoint = 'vendors'; 12 | vendor = {}; 13 | 14 | constructor(http, config, router, session, geo) { 15 | this.http = http; 16 | this.config = config; 17 | this.router = router; 18 | this.session = session; 19 | this.geo = geo; 20 | } 21 | 22 | async createVendor() { 23 | try { 24 | this.vendor.location = await this.geo.getLatLongForAddress(this.vendor.address); 25 | this.vendor.user = this.session.currentUser._id; 26 | let newVendor = await this.http.post(`${this.config.server.url}${this.endPoint}`, this.vendor); 27 | this.router.navigateToRoute('vendorById', {id: newVendor.content._id}); 28 | } catch (err) { 29 | // TODO flash a global error message 30 | console.log('error connecting: ', err); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/build/tasks/e2e.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var to5 = require('gulp-babel'); 4 | var plumber = require('gulp-plumber'); 5 | var gulp = require('gulp'); 6 | var webdriver_update = require('gulp-protractor').webdriver_update; 7 | var protractor = require("gulp-protractor").protractor; 8 | 9 | // for full documentation of gulp-protractor, 10 | // please check https://github.com/mllrsohn/gulp-protractor 11 | gulp.task('webdriver_update', webdriver_update); 12 | 13 | // transpiles files in 14 | // /test/e2e/src/ from es6 to es5 15 | // then copies them to test/e2e/dist/ 16 | gulp.task('build-e2e', function () { 17 | return gulp.src(paths.e2eSpecsSrc) 18 | .pipe(plumber()) 19 | .pipe(to5()) 20 | .pipe(gulp.dest(paths.e2eSpecsDist)); 21 | }); 22 | 23 | // runs build-e2e task 24 | // then runs end to end tasks 25 | // using Protractor: http://angular.github.io/protractor/ 26 | gulp.task('e2e', ['webdriver_update', 'build-e2e'], function(cb) { 27 | return gulp.src(paths.e2eSpecsDist + "/*.js") 28 | .pipe(protractor({ 29 | configFile: "protractor.conf.js", 30 | args: ['--baseUrl', 'http://127.0.0.1:9000'] 31 | })) 32 | .on('error', function(e) { throw e; }); 33 | }); 34 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "globals": { 4 | "$": true 5 | }, 6 | "rules": { 7 | "strict": 0, 8 | "no-underscore-dangle": 0, 9 | "no-multi-spaces": 0, 10 | "key-spacing": 0, 11 | "no-shadow": 0, 12 | "consistent-return": 2, 13 | "no-use-before-define": 2, 14 | "new-parens": 2, 15 | "no-cond-assign": 2, 16 | "space-after-keywords": 2, 17 | "space-infix-ops": 2, 18 | "comma-dangle": [2, "never"], 19 | "no-multiple-empty-lines": [2, { 20 | "max": 2 21 | }], 22 | "handle-callback-err": [0], 23 | "quotes": [0, "single"], 24 | "eqeqeq": [2, "smart"], 25 | "wrap-iife": [2, "inside"], 26 | "indent": [2, 2], 27 | "brace-style": [2, "1tbs"], 28 | "spaced-line-comment": [0, "always", { 29 | "exceptions": ["-", "+"] 30 | }], 31 | "space-before-function-parentheses": [2, { 32 | "anonymous": "always", 33 | "named": "never" 34 | }], 35 | "new-cap": 0 36 | }, 37 | "ecmaFeatures": { 38 | "modules": true 39 | }, 40 | "env": { 41 | "node": true, 42 | "es6": true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/test/e2e/dist/skeleton.po.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | var PageObject_Skeleton = exports.PageObject_Skeleton = (function () { 8 | function PageObject_Skeleton() { 9 | _classCallCheck(this, PageObject_Skeleton); 10 | } 11 | 12 | _prototypeProperties(PageObject_Skeleton, null, { 13 | getCurrentPageTitle: { 14 | value: function getCurrentPageTitle() { 15 | return browser.getTitle(); 16 | }, 17 | writable: true, 18 | configurable: true 19 | }, 20 | navigateTo: { 21 | value: function navigateTo(href) { 22 | element(by.css("a[href=\"" + href + "\"]")).click(); 23 | return browser.waitForHttpDone(); 24 | }, 25 | writable: true, 26 | configurable: true 27 | } 28 | }); 29 | 30 | return PageObject_Skeleton; 31 | })(); 32 | 33 | Object.defineProperty(exports, "__esModule", { 34 | value: true 35 | }); -------------------------------------------------------------------------------- /client/build/tasks/prepare-release.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | var paths = require('../paths'); 4 | var changelog = require('conventional-changelog'); 5 | var fs = require('fs'); 6 | var bump = require('gulp-bump'); 7 | var args = require('../args'); 8 | 9 | // utilizes the bump plugin to bump the 10 | // semver for the repo 11 | gulp.task('bump-version', function(){ 12 | return gulp.src(['./package.json']) 13 | .pipe(bump({type:args.bump })) //major|minor|patch|prerelease 14 | .pipe(gulp.dest('./')); 15 | }); 16 | 17 | // generates the CHANGELOG.md file based on commit 18 | // from git commit messages 19 | gulp.task('changelog', function(callback) { 20 | var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8')); 21 | 22 | return changelog({ 23 | repository: pkg.repository.url, 24 | version: pkg.version, 25 | file: paths.doc + '/CHANGELOG.md' 26 | }, function(err, log) { 27 | fs.writeFileSync(paths.doc + '/CHANGELOG.md', log); 28 | }); 29 | }); 30 | 31 | // calls the listed sequence of tasks in order 32 | gulp.task('prepare-release', function(callback){ 33 | return runSequence( 34 | 'build', 35 | 'lint', 36 | 'bump-version', 37 | 'doc', 38 | 'changelog', 39 | callback 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /client/src/routes/log/new.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /client/src/routes/vendors/photo-sphere.js: -------------------------------------------------------------------------------- 1 | import {customElement, bindable, inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | import {EventAggregator} from 'aurelia-event-aggregator'; 5 | 6 | 7 | @customElement('photo-sphere') 8 | @inject(HttpClient, Config, EventAggregator) 9 | export class VendorServiceAdd { 10 | showingPhotoSphere = false; 11 | showingSphere1 = true; 12 | showingSphere2 = false; 13 | showingStreetView = false; 14 | 15 | nextSphere() { 16 | this.showingSphere1 = false; 17 | this.showingStreetView = false; 18 | this.showingSphere2 = true; 19 | } 20 | 21 | prevSphere() { 22 | this.showingSphere2 = false; 23 | this.showingStreetView = false; 24 | this.showingSphere1 = true; 25 | } 26 | 27 | showStreetView() { 28 | this.showingSphere2 = false; 29 | this.showingSphere1 = false; 30 | this.showingStreetView = true; 31 | } 32 | 33 | closePhotoSphere() { 34 | this.showingPhotoSphere = false; 35 | } 36 | 37 | openPhotoSphere() { 38 | this.showingPhotoSphere = true; 39 | } 40 | 41 | 42 | 43 | 44 | constructor(http, config, ea) { 45 | this.http = http; 46 | this.config = config; 47 | this.ea = ea; 48 | // this.vendor = vendor.vendor; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Aurelia 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules" : { 4 | "strict" : 0, 5 | "no-underscore-dangle" : 0, 6 | "no-multi-spaces" : 0, 7 | "key-spacing" : 0, 8 | "no-shadow" : 0, 9 | "consistent-return" : 2, 10 | "no-use-before-define" : 2, 11 | "new-parens" : 2, 12 | "no-cond-assign" : 2, 13 | "space-after-keywords" : 2, 14 | "space-infix-ops" : 2, 15 | "comma-dangle" : [2, "never"], 16 | "no-multiple-empty-lines" : [2, {"max": 2}], 17 | "quotes" : [2, "single"], 18 | "eqeqeq" : [2, "smart"], 19 | "wrap-iife" : [2, "inside"], 20 | "indent" : [2, 2], 21 | "brace-style" : [2, "1tbs"], 22 | "spaced-line-comment" : [2, "always", {"exceptions":["-","+"]}], 23 | "space-before-function-parentheses": [2, { 24 | "anonymous" : "always", 25 | "named" : "never" 26 | }] 27 | }, 28 | "env": { 29 | "node": true, 30 | "es6" : true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/routes/home/index.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /client/build/tasks/less.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var less = require('gulp-less'); 3 | var path = require('path'); 4 | var autoprefixer = require('gulp-autoprefixer'); 5 | var minify = require('gulp-minify-css'); 6 | var resolve = require("resolve-dep"); 7 | var deepExtend = require("deep-extend"); 8 | var sourcemaps = require('gulp-sourcemaps'); 9 | var paths = require('../paths'); 10 | 11 | var LessPluginAutoPrefix = require('less-plugin-autoprefix'); 12 | var LessPluginCleanCSS = require('less-plugin-clean-css'); 13 | 14 | var cleancss = new LessPluginCleanCSS({ advanced: true }); 15 | var autoprefix= new LessPluginAutoPrefix({ browsers: ["last 2 versions"] }); 16 | 17 | gulp.task('less', function(){ 18 | return compileFile(paths.styleEntry, paths.output, {compress: true}); 19 | }); 20 | 21 | /** 22 | * Compile a less file 23 | */ 24 | function compileFile(input, output, options){ 25 | return gulp.src(input) 26 | .pipe(sourcemaps.init()) 27 | .pipe(less(deepExtend({ 28 | filename: input, 29 | paths: [ 30 | '.', 31 | path.dirname(path.dirname(resolve.npm('bootstrap-less')[0])) 32 | ] 33 | // plugins: [cleancss,autoprefix] 34 | }, options))) 35 | 36 | // .pipe(autoprefixer({browsers: ['last 3 versions']})) 37 | .pipe(minify({restructuring: false, keepBreaks: !options.compress})) 38 | .pipe(sourcemaps.write(".")) 39 | .pipe(gulp.dest(output)); 40 | } 41 | -------------------------------------------------------------------------------- /client/test/e2e/src/demo.spec.js: -------------------------------------------------------------------------------- 1 | import {PageObject_Welcome} from './welcome.po.js'; 2 | import {PageObject_Skeleton} from './skeleton.po.js'; 3 | 4 | describe('aurelia skeleton app', function() { 5 | var po_welcome, 6 | po_skeleton; 7 | 8 | beforeEach( () => { 9 | po_skeleton = new PageObject_Skeleton(); 10 | po_welcome = new PageObject_Welcome(); 11 | 12 | browser.loadAndWaitForAureliaPage("http://localhost:9000"); 13 | }); 14 | 15 | it('should load the page and display the initial page title', () => { 16 | expect(po_skeleton.getCurrentPageTitle()).toBe('Welcome | Aurelia'); 17 | }); 18 | 19 | it('should display greeting', () => { 20 | expect(po_welcome.getGreeting()).toBe('Welcome to the Aurelia Navigation App!'); 21 | }); 22 | 23 | it('should automatically write down the fullname', () => { 24 | po_welcome.setFirstname('Rob'); 25 | po_welcome.setLastname('Eisenberg'); 26 | 27 | // For now there is a timing issue with the binding. 28 | // Until resolved we will use a short sleep to overcome the issue. 29 | browser.sleep(200); 30 | expect(po_welcome.getFullname()).toBe('ROB EISENBERG'); 31 | }); 32 | 33 | it('should show alert message when clicking submit button', () => { 34 | expect(po_welcome.openAlertDialog()).toBe(true); 35 | }); 36 | 37 | it('should navigate to flickr page', () => { 38 | po_skeleton.navigateTo('#/flickr'); 39 | expect(po_skeleton.getCurrentPageTitle()).toBe('Flickr | Aurelia'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /client/test/e2e/dist/demo.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var PageObject_Welcome = require("./welcome.po.js").PageObject_Welcome; 4 | 5 | var PageObject_Skeleton = require("./skeleton.po.js").PageObject_Skeleton; 6 | 7 | describe("aurelia skeleton app", function () { 8 | var po_welcome, po_skeleton; 9 | 10 | beforeEach(function () { 11 | po_skeleton = new PageObject_Skeleton(); 12 | po_welcome = new PageObject_Welcome(); 13 | 14 | browser.loadAndWaitForAureliaPage("http://localhost:9000"); 15 | }); 16 | 17 | it("should load the page and display the initial page title", function () { 18 | expect(po_skeleton.getCurrentPageTitle()).toBe("Welcome | Aurelia"); 19 | }); 20 | 21 | it("should display greeting", function () { 22 | expect(po_welcome.getGreeting()).toBe("Welcome to the Aurelia Navigation App!"); 23 | }); 24 | 25 | it("should automatically write down the fullname", function () { 26 | po_welcome.setFirstname("Rob"); 27 | po_welcome.setLastname("Eisenberg"); 28 | 29 | // For now there is a timing issue with the binding. 30 | // Until resolved we will use a short sleep to overcome the issue. 31 | browser.sleep(200); 32 | expect(po_welcome.getFullname()).toBe("ROB EISENBERG"); 33 | }); 34 | 35 | it("should show alert message when clicking submit button", function () { 36 | expect(po_welcome.openAlertDialog()).toBe(true); 37 | }); 38 | 39 | it("should navigate to flickr page", function () { 40 | po_skeleton.navigateTo("#/flickr"); 41 | expect(po_skeleton.getCurrentPageTitle()).toBe("Flickr | Aurelia"); 42 | }); 43 | }); -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Koa + Mongoose", 6 | "keywords": [], 7 | "dependencies": { 8 | "babel": "^5.4.7", 9 | "bcrypt": "^0.8.0", 10 | "co": "4.5.4", 11 | "co-mongo": "0.0.2", 12 | "co-request": "0.2.1", 13 | "gm": "^1.17.0", 14 | "include-all": "~0.1.3", 15 | "jsonwebtoken": "^3.2.2", 16 | "kcors": "^1.0.1", 17 | "koa": "^0.5.2", 18 | "koa-bodyparser": "^2.0.0", 19 | "koa-conditional-get": "^1.0.3", 20 | "koa-etag": "^2.0.0", 21 | "koa-jwt": "1.0.0", 22 | "koa-logger": "1.2.2", 23 | "koa-mongo-rest": "0.3.4", 24 | "koa-route": "2.4.1", 25 | "koa-router": "^3.1.2", 26 | "koa-send": "1.3.1", 27 | "lodash": "3.9.1", 28 | "mongoose": "4.0.3", 29 | "mongoose-autopopulate": "^0.1.0", 30 | "mongoose-bcrypt": "^1.1.1", 31 | "mongoose-geojson-schema": "0.0.2", 32 | "mongoose-hidden": "^0.4.1", 33 | "mongoose-permalink": "^2.0.0", 34 | "mongoose-simpletimestamps": "^1.0.2", 35 | "nimble": "0.0.2", 36 | "pluralize": "^1.1.2" 37 | }, 38 | "scripts": { 39 | "test": "node tests/runner.js", 40 | "start": "node server.js", 41 | "debug": "node debug server.js" 42 | }, 43 | "main": "server.js", 44 | "author": "martin.genev", 45 | "license": "", 46 | "devDependencies": { 47 | "babel-core": "^5.4.7", 48 | "babel-eslint": "^2.0.2", 49 | "barrels": "^1.4.1", 50 | "chai": "^2.1.0", 51 | "glob": "^4.4.1", 52 | "mocha": "^2.1.0", 53 | "sinon": "^1.12.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/routes/vendors/new.html: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-list.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Project Nautilus 4 | ========================== 5 | Building on the lessons of previous stacks, Nautilus aims to explore the latest advances in full stack Javascript for web development with the goal of being lightweight, versatile and powerful. 6 | 7 | #### Core components 8 | + Aurelia - Client Side Framework 9 | + Node - Server Side 10 | + Koa - Web Server Framework 11 | + Mongoose - Node-Mongo ORM 12 | + MongoDB - Database 13 | 14 | #### Additional Tools Available 15 | + Bootstrap - front end framework 16 | + GraphicMagic - Image manipulation 17 | 18 | #### Currently demonstrated features: 19 | - Geo service for Google Maps 20 | - Automatically generated REST API for each model 21 | 22 | #### Open to contributors 23 | ========================== 24 | This project is made for learning. There is no better experience for that than doing. We strongly encourage you to get involved in the project. If you're new to development or new to these technologies, this gives you the unique opportunity to join a real team of experienced professionals which uses best practices and adopts the latest in the field to solve real world requirements. It can teach you to make web apps better than any school or book. 25 | 26 | #### Installation: 27 | ========================== 28 | 1. Go to the client folder and follow the steps to install Aurelia's prerequisites Gulp and Jspm http://aurelia.io/get-started.html 29 | 2. `npm install` in both /client and /server folders 30 | 3. `jspm install -y` in /client 31 | 4. run server with ./server node run 32 | 5. run client with ./client/gulp watch 33 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/forms.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Form 3 | * --------------- 4 | */ 5 | .form-control { 6 | .border-radius(@input-radius)!important; 7 | box-shadow: none; 8 | border-color: @gray; 9 | &:focus { 10 | border-color: @light-blue !important; 11 | box-shadow: none; 12 | } 13 | &::-moz-placeholder { 14 | color: #bbb; 15 | opacity: 1; 16 | } 17 | &:-ms-input-placeholder { 18 | color: #bbb; 19 | } 20 | &::-webkit-input-placeholder { 21 | color: #bbb; 22 | } 23 | 24 | &:not(select) { 25 | -webkit-appearance: none; 26 | -moz-appearance: none; 27 | appearance: none; 28 | } 29 | } 30 | 31 | .form-group { 32 | &.has-success { 33 | label { 34 | color: @green; 35 | } 36 | .form-control { 37 | border-color: @green !important; 38 | box-shadow: none; 39 | } 40 | } 41 | 42 | &.has-warning { 43 | label { 44 | color: @yellow; 45 | } 46 | .form-control { 47 | border-color: @yellow !important; 48 | box-shadow: none; 49 | } 50 | } 51 | 52 | &.has-error { 53 | label { 54 | color: @red; 55 | } 56 | .form-control { 57 | border-color: @red !important; 58 | box-shadow: none; 59 | } 60 | } 61 | } 62 | 63 | /* Input group */ 64 | .input-group { 65 | .input-group-addon { 66 | .border-radius(@input-radius); 67 | border-color: @gray; 68 | background-color: #fff; 69 | } 70 | } 71 | /* button groups */ 72 | .btn-group-vertical { 73 | .btn { 74 | &.btn-flat:first-of-type, &.btn-flat:last-of-type { 75 | .border-radius(0); 76 | } 77 | } 78 | } 79 | 80 | .icheck > label { 81 | padding-left: 0; 82 | } -------------------------------------------------------------------------------- /client/src/routes/vendors/services/service-add.js: -------------------------------------------------------------------------------- 1 | import {customElement, bindable, inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | import {EventAggregator} from 'aurelia-event-aggregator'; 5 | 6 | @customElement('service-add') 7 | @inject(HttpClient, Config, EventAggregator) 8 | export class SayHello { 9 | constructor(http, config, ea) { 10 | this.http = http; 11 | this.config = config; 12 | this.ea = ea; 13 | } 14 | 15 | @bindable vendor = null; 16 | endPoint = 'services'; 17 | service = {test: 'testis'}; 18 | serviceAddSteps = [{ 19 | path: 'routes/vendors/services/service-add-basic-info.html', 20 | caption : 'Basic Info' 21 | }, 22 | { 23 | path: 'routes/vendors/services/service-add-prices.html', 24 | caption : 'Prices and Capacity' 25 | }, 26 | { 27 | path: 'routes/vendors/services/service-add-terms.html', 28 | caption : 'Terms of Agreement' 29 | }]; 30 | showingAddService = false; 31 | closeModal() { 32 | this.showingAddService = false; 33 | } 34 | openModal() { 35 | this.showingAddService = true; 36 | } 37 | 38 | async createService() { 39 | try { 40 | this.service.vendor = this.vendor._id; 41 | let newService = await this.http.post(`${this.config.server.url}${this.endPoint}`, this.service); 42 | 43 | for (let prop in this.service) { 44 | this.service[prop] = ''; 45 | } 46 | this.closeModal(); 47 | this.ea.publish('vendors:serviceCreated', newService.content); 48 | } catch (err) { 49 | // TODO flash a global error message 50 | console.log('error creating service: ', err); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/src/routes/vendors/photo-sphere.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /client/src/routes/geo/index.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {GeoGoogleService} from 'services/geo-google'; 3 | import {EventAggregator} from 'aurelia-event-aggregator'; 4 | 5 | @inject(GeoGoogleService, EventAggregator) 6 | export class GeoIndexRoute { 7 | heading = 'Geo Demo'; 8 | constructor(geo, eventAggregator) { 9 | this.geo = geo; 10 | this.eventAggregator = eventAggregator; 11 | } 12 | 13 | attached () { 14 | this.drawMap(); 15 | } 16 | 17 | activate() { 18 | this.setGeo(); 19 | this.eventAggregator.subscribe('googleMaps:markerClick', index => this.highlightPlace(index)); 20 | } 21 | 22 | async setGeo() { 23 | try { 24 | this.geoData = await this.geo.getGeoposition(); 25 | this.address = await this.geo.getAddressForLatLong(this.geoData); 26 | } catch (err) { 27 | console.log('ERR: ' , err); 28 | } 29 | } 30 | 31 | drawMap() { 32 | let opts = { 33 | geo: this.geoData, 34 | mapElementSelector: 'mapfeed', 35 | pinCenter: true, 36 | type: 'ROADMAP' 37 | }; 38 | this.geo.drawMap(opts); 39 | } 40 | 41 | async searchPlaces() { 42 | this.geo.clearMarkers(); 43 | this.places = []; 44 | let opts = { 45 | geo: this.geoData, 46 | radius: 500, 47 | query: this.query, 48 | pinMarkers: true 49 | }; 50 | 51 | try { 52 | this.places = await this.geo.getNearbyPlaces(opts); 53 | console.log(this.places); 54 | } catch (err) { 55 | console.log('ERR: ', err); 56 | } 57 | } 58 | 59 | highlightPlace(index) { 60 | if (this.activeClass !== undefined) { 61 | this.places[this.activeClass].class=''; 62 | } 63 | this.activeClass=index; 64 | this.places[index].class='place-active'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/src/routes/geo/index.html: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /client/build/tasks/build.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | var changed = require('gulp-changed'); 4 | var plumber = require('gulp-plumber'); 5 | var less = require('gulp-less'); 6 | var to5 = require('gulp-babel'); 7 | var sourcemaps = require('gulp-sourcemaps'); 8 | var paths = require('../paths'); 9 | var compilerOptions = require('../babel-options'); 10 | var assign = Object.assign || require('object.assign'); 11 | 12 | // transpiles changed es6 files to SystemJS format 13 | // the plumber() call prevents 'pipe breaking' caused 14 | // by errors from other gulp plugins 15 | // https://www.npmjs.com/package/gulp-plumber 16 | gulp.task('build-system', function () { 17 | return gulp.src(paths.source) 18 | .pipe(plumber()) 19 | .pipe(changed(paths.output, {extension: '.js'})) 20 | .pipe(sourcemaps.init({loadMaps: true})) 21 | .pipe(to5(assign({}, compilerOptions, {modules:'system'}))) 22 | .pipe(sourcemaps.write({includeContent: false, sourceRoot: paths.sourceMapRelativePath })) 23 | .pipe(gulp.dest(paths.output)); 24 | }); 25 | 26 | // copies changed html files to the output directory 27 | gulp.task('build-html', function () { 28 | return gulp.src(paths.html) 29 | .pipe(changed(paths.output, {extension: '.html'})) 30 | .pipe(gulp.dest(paths.output)); 31 | }); 32 | 33 | gulp.task('build-style', function () { 34 | return gulp.src(paths.style) 35 | .pipe(less()) 36 | .pipe(gulp.dest(paths.output)); 37 | }); 38 | 39 | 40 | // this task calls the clean task (located 41 | // in ./clean.js), then runs the build-system 42 | // and build-html tasks in parallel 43 | // https://www.npmjs.com/package/gulp-run-sequence 44 | gulp.task('build', function(callback) { 45 | return runSequence( 46 | 'clean', 47 | ['build-system', 'build-html'], 48 | ['less'], 49 | callback 50 | ); 51 | }); 52 | -------------------------------------------------------------------------------- /client/aurelia.protractor.js: -------------------------------------------------------------------------------- 1 | /* Aurelia Protractor Plugin */ 2 | function addValueBindLocator() { 3 | by.addLocator('valueBind', function (bindingModel, opt_parentElement) { 4 | var using = opt_parentElement || document; 5 | var matches = using.querySelectorAll('*[value\\.bind="' + bindingModel +'"]'); 6 | var result; 7 | 8 | if (matches.length === 0) { 9 | result = null; 10 | } else if (matches.length === 1) { 11 | result = matches[0]; 12 | } else { 13 | result = matches; 14 | } 15 | 16 | return result; 17 | }); 18 | } 19 | 20 | function loadAndWaitForAureliaPage(pageUrl) { 21 | browser.get(pageUrl); 22 | return browser.executeAsyncScript( 23 | 'var cb = arguments[arguments.length - 1];' + 24 | 'document.addEventListener("aurelia-composed", function (e) {' + 25 | ' cb("Aurelia App composed")' + 26 | '}, false);' 27 | ).then(function(result){ 28 | console.log(result); 29 | return result; 30 | }); 31 | } 32 | 33 | function waitForHttpDone() { 34 | return browser.executeAsyncScript( 35 | 'var cb = arguments[arguments.length - 1];' + 36 | 'document.addEventListener("aurelia-http-client-requests-drained", function (e) {' + 37 | ' cb(true)' + 38 | '}, false);' 39 | ).then(function(result){ 40 | return result; 41 | }); 42 | } 43 | 44 | /* Plugin hooks */ 45 | exports.setup = function(config) { 46 | // Ignore the default Angular synchronization helpers 47 | browser.ignoreSynchronization = true; 48 | 49 | // add the aurelia specific valueBind locator 50 | addValueBindLocator(); 51 | 52 | // attach a new way to browser.get a page and wait for Aurelia to complete loading 53 | browser.loadAndWaitForAureliaPage = loadAndWaitForAureliaPage; 54 | 55 | // wait for all http requests to finish 56 | browser.waitForHttpDone = waitForHttpDone; 57 | }; 58 | 59 | exports.teardown = function(config) {}; 60 | exports.postResults = function(config) {}; 61 | -------------------------------------------------------------------------------- /client/src/routes/vendors/vendor.js: -------------------------------------------------------------------------------- 1 | import {inject, bindable} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {Config} from 'services/config'; 4 | import {EventAggregator} from 'aurelia-event-aggregator'; 5 | import {GeoGoogleService} from 'services/geo-google'; 6 | 7 | @inject(HttpClient, Config, EventAggregator, GeoGoogleService) 8 | // TODO rename to vendorRoute and all other routes too 9 | export class VendorRoute { 10 | heading = 'Single Vendor'; 11 | 12 | readyToDrawMap = { 13 | domReady: false, 14 | vendorDataReady: false 15 | }; 16 | 17 | constructor(http, config, ea, geo){ 18 | this.http = http; 19 | this.config = config; 20 | this.ea = ea; 21 | this.geo = geo; 22 | this.ea.subscribe('vendors:serviceCreated', service => { 23 | this.services.push(service); 24 | }); 25 | } 26 | 27 | attached () { 28 | this.readyToDrawMap.domReady = true; 29 | this.drawMap(); 30 | } 31 | 32 | async activate(params, routeConfig) { 33 | try { 34 | let query = `{"vendor": "${params.id}"}`; 35 | let vendor = await this.http.get(`${this.config.server.url}vendors/${params.id}`); 36 | let services = await this.http.get(`${this.config.server.url}services?conditions=${query}`); 37 | this.vendor = vendor.content; 38 | this.services = services.content; 39 | routeConfig.navModel.title = this.vendor.name; 40 | this.readyToDrawMap.vendorDataReady = true; 41 | this.drawMap(); 42 | } catch (err) { 43 | // TODO flash a global error message 44 | console.log('error connecting: ', err); 45 | } 46 | } 47 | 48 | drawMap() { 49 | if (this.readyToDrawMap.domReady && this.readyToDrawMap.vendorDataReady) { 50 | let opts = { 51 | geo: {latitude: this.vendor.location.coordinates[1], longitude: this.vendor.location.coordinates[0]}, 52 | mapElementSelector: 'mapfeed', 53 | pinCenter: true, 54 | type: 'ROADMAP' 55 | }; 56 | this.geo.drawMap(opts); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Dec 05 2014 16:49:29 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jspm', 'jasmine'], 14 | 15 | jspm: { 16 | // Edit this to your needs 17 | loadFiles: ['src/**/*.js', 'test/unit/**/*.js'] 18 | }, 19 | 20 | 21 | // list of files / patterns to load in the browser 22 | files: [], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | 'test/**/*.js': ['babel'], 34 | 'src/**/*.js': ['babel'] 35 | }, 36 | 'babelPreprocessor': { 37 | options: { 38 | sourceMap: 'inline', 39 | modules: 'system', 40 | moduleIds: false, 41 | optional: [ 42 | "es7.decorators", 43 | "es7.classProperties" 44 | ] 45 | } 46 | }, 47 | 48 | // test results reporter to use 49 | // possible values: 'dots', 'progress' 50 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 51 | reporters: ['progress'], 52 | 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | 58 | // enable / disable colors in the output (reporters and logs) 59 | colors: true, 60 | 61 | 62 | // level of logging 63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | 67 | // enable / disable watching file and executing tests whenever any file changes 68 | autoWatch: true, 69 | 70 | 71 | // start these browsers 72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 73 | browsers: ['Chrome'], 74 | 75 | 76 | // Continuous Integration mode 77 | // if true, Karma captures browsers, runs the tests and exits 78 | singleRun: false 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/progress-bars.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Progress Bar 3 | * ----------------------- 4 | */ 5 | 6 | //General CSS 7 | .progress, 8 | .progress > .progress-bar { 9 | .box-shadow(none); 10 | &, .progress-bar { 11 | .border-radius(@progress-bar-border-radius); 12 | } 13 | } 14 | 15 | /* size variation */ 16 | .progress.sm, 17 | .progress-sm { 18 | height: 10px; 19 | &, .progress-bar { 20 | .border-radius(@progress-bar-sm-border-radius); 21 | } 22 | } 23 | .progress.xs, 24 | .progress-xs { 25 | height: 7px; 26 | &, .progress-bar { 27 | .border-radius(@progress-bar-xs-border-radius); 28 | } 29 | } 30 | .progress.xxs, 31 | .progress-xxs { 32 | height: 3px; 33 | &, .progress-bar { 34 | .border-radius(@progress-bar-xs-border-radius); 35 | } 36 | } 37 | /* Vertical bars */ 38 | .progress.vertical { 39 | position: relative; 40 | width: 30px; 41 | height: 200px; 42 | display: inline-block; 43 | margin-right: 10px; 44 | > .progress-bar { 45 | width: 100%!important; 46 | position: absolute; 47 | bottom: 0; 48 | } 49 | 50 | //Sizes 51 | &.sm, 52 | &.progress-sm{ 53 | width: 20px; 54 | } 55 | 56 | &.xs, 57 | &.progress-xs{ 58 | width: 10px; 59 | } 60 | &.xxs, 61 | &.progress-xxs{ 62 | width: 3px; 63 | } 64 | } 65 | 66 | //Progress Groups 67 | .progress-group { 68 | .progress-text { 69 | font-weight: 600; 70 | } 71 | .progress-number { 72 | float: right; 73 | } 74 | } 75 | 76 | /* Remove margins from progress bars when put in a table */ 77 | .table { 78 | tr > td .progress { 79 | margin: 0; 80 | } 81 | } 82 | 83 | // Variations 84 | // ------------------------- 85 | .progress-bar-light-blue, 86 | .progress-bar-primary { 87 | .progress-bar-variant(@light-blue); 88 | } 89 | .progress-bar-green, 90 | .progress-bar-success { 91 | .progress-bar-variant(@green); 92 | } 93 | 94 | .progress-bar-aqua, 95 | .progress-bar-info { 96 | .progress-bar-variant(@aqua); 97 | } 98 | 99 | .progress-bar-yellow, 100 | .progress-bar-warning { 101 | .progress-bar-variant(@yellow); 102 | } 103 | 104 | .progress-bar-red, 105 | .progress-bar-danger { 106 | .progress-bar-variant(@red); 107 | } -------------------------------------------------------------------------------- /client/src/routes/vendors/vendor.html: -------------------------------------------------------------------------------- 1 | 54 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-left/side-bar-left.html: -------------------------------------------------------------------------------- 1 | 64 | -------------------------------------------------------------------------------- /client/test/e2e/dist/welcome.po.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | var PageObject_Welcome = exports.PageObject_Welcome = (function () { 8 | function PageObject_Welcome() { 9 | _classCallCheck(this, PageObject_Welcome); 10 | } 11 | 12 | _prototypeProperties(PageObject_Welcome, null, { 13 | getGreeting: { 14 | value: function getGreeting() { 15 | return element(by.tagName("h2")).getText(); 16 | }, 17 | writable: true, 18 | configurable: true 19 | }, 20 | setFirstname: { 21 | value: function setFirstname(value) { 22 | return element(by.valueBind("firstName")).clear().sendKeys(value); 23 | }, 24 | writable: true, 25 | configurable: true 26 | }, 27 | setLastname: { 28 | value: function setLastname(value) { 29 | return element(by.valueBind("lastName")).clear().sendKeys(value); 30 | }, 31 | writable: true, 32 | configurable: true 33 | }, 34 | getFullname: { 35 | value: function getFullname() { 36 | return element(by.css(".help-block")).getText(); 37 | }, 38 | writable: true, 39 | configurable: true 40 | }, 41 | pressSubmitButton: { 42 | value: function pressSubmitButton() { 43 | return element(by.css("button[type=\"submit\"]")).click(); 44 | }, 45 | writable: true, 46 | configurable: true 47 | }, 48 | openAlertDialog: { 49 | value: function openAlertDialog() { 50 | var _this = this; 51 | 52 | return browser.wait(function () { 53 | _this.pressSubmitButton(); 54 | 55 | return browser.switchTo().alert().then( 56 | // use alert.accept instead of alert.dismiss which results in a browser crash 57 | function (alert) { 58 | alert.accept();return true; 59 | }, function () { 60 | return false; 61 | }); 62 | }); 63 | }, 64 | writable: true, 65 | configurable: true 66 | } 67 | }); 68 | 69 | return PageObject_Welcome; 70 | })(); 71 | 72 | Object.defineProperty(exports, "__esModule", { 73 | value: true 74 | }); -------------------------------------------------------------------------------- /server/config/environment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Environment variables and application configuration. 3 | */ 4 | 5 | import path from 'path'; 6 | import _ from 'lodash'; 7 | 8 | var baseConfig = { 9 | app: { 10 | apiPrefix: 'api', 11 | root: path.normalize(__dirname + '/../..'), 12 | env: process.env.NODE_ENV, 13 | secret: process.env.SECRET || 'secret key' /* used in signing the jwt tokens */ 14 | }, 15 | secret: 'mahsecret' 16 | }; 17 | 18 | // environment specific config overrides 19 | var platformConfig = { 20 | development: { 21 | app: { 22 | port: 3000 23 | }, 24 | mongo: { 25 | url: 'mongodb://localhost:27017/nautilus' 26 | }, 27 | oauth: { 28 | facebook: { 29 | clientId: '231235687068678', 30 | clientSecret: process.env.FACEBOOK_SECRET || '4a90381c6bfa738bb18fb7d6046c14b8', 31 | callbackUrl: 'http://localhost:3000/signin/facebook/callback' 32 | }, 33 | google: { 34 | clientId: '147832090796-ckhu1ehvsc8vv9nso7iefvu5fi7jrsou.apps.googleusercontent.com', 35 | clientSecret: process.env.GOOGLE_SECRET || 'MGOwKgcLPEfCsLjcJJSPeFYu', 36 | callbackUrl: 'http://localhost:3000/signin/google/callback' 37 | } 38 | } 39 | }, 40 | 41 | test: { 42 | app: { 43 | port: 3001 44 | }, 45 | mongo: { 46 | url: 'mongodb://localhost:27017/nautilus-test' 47 | } 48 | }, 49 | 50 | production: { 51 | app: { 52 | port: process.env.PORT || 3000, 53 | cacheTime: 7 * 24 * 60 * 60 * 1000 /* default caching time (7 days) for static files, calculated in milliseconds */ 54 | }, 55 | mongo: { 56 | url: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost:27017/koan' 57 | }, 58 | oauth: { 59 | facebook: { 60 | clientId: '231235687068678', 61 | clientSecret: process.env.FACEBOOK_SECRET || '4a90381c6bfa738bb18fb7d6046c14b8', 62 | callbackUrl: 'https://koan.herokuapp.com/signin/facebook/callback' 63 | }, 64 | google: { 65 | clientId: '147832090796-ckhu1ehvsc8vv9nso7iefvu5fi7jrsou.apps.googleusercontent.com', 66 | clientSecret: process.env.GOOGLE_SECRET || 'MGOwKgcLPEfCsLjcJJSPeFYu', 67 | callbackUrl: 'https://koan.herokuapp.com/signin/google/callback' 68 | } 69 | } 70 | } 71 | }; 72 | 73 | // override the base configuration with the platform specific values 74 | module.exports = _.merge(baseConfig, platformConfig[baseConfig.app.env || (baseConfig.app.env = 'development')]); 75 | -------------------------------------------------------------------------------- /server/config/koa.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import logger from 'koa-logger'; 3 | import send from 'koa-send'; 4 | import cors from 'kcors'; 5 | import jwt from 'koa-jwt'; 6 | import conditional from 'koa-conditional-get'; 7 | import bodyParser from 'koa-bodyparser'; 8 | import etag from 'koa-etag'; 9 | import router from'koa-router'; 10 | import config from './environment'; 11 | import authExempt from './auth-exempt'; 12 | import authCheck from './auth-check'; 13 | import authenticate from './authenticate'; 14 | import generateApi from 'koa-mongo-rest'; 15 | import pluralize from 'pluralize'; 16 | import mongoose from 'mongoose'; 17 | 18 | module.exports = function (app) { 19 | // middleware configuration 20 | app.use(responseTime); 21 | app.use(cors()); 22 | app.use(conditional()); 23 | app.use(etag()); 24 | app.use(bodyParser()); 25 | app.use(authenticate); 26 | app.use(router(app)); 27 | app.use(logger()); 28 | 29 | // create all models first so controllers have them available 30 | let model, schema; 31 | for (let name of require('fs').readdirSync(__dirname+'/../models')) { 32 | if (name[0] === '.') return; 33 | name = name.substring(0, name.length - 3); 34 | schema = require('../models/' + name); 35 | model = mongoose.model(name, schema); 36 | }; 37 | 38 | // auto mount all the simple routes defined in the api controllers 39 | // initialize complex custom defined routes 40 | // if route is found in exempt, do not run auth check 41 | for (let fileName of fs.readdirSync(__dirname+'/../controllers')) { 42 | let controller = require(__dirname+'/../controllers/' + fileName); 43 | fileName = fileName.substring(0, fileName.length - 3); 44 | for (let propName in controller) { 45 | if (propName === 'init') { 46 | controller.init(app); 47 | } else { 48 | let arr = propName.split("_"); 49 | let methodName = arr[0]; 50 | let handlerName = arr[1]; 51 | 52 | let exempt = false; 53 | for (let endpoint in authExempt) { 54 | for (let method of authExempt[endpoint]) { 55 | (fileName === endpoint && handlerName === method) ? exempt = true : false; 56 | } 57 | } 58 | let pathName = `/${config.app.apiPrefix}/${pluralize(fileName)}/${handlerName}`; 59 | exempt ? app[methodName](pathName, controller[propName]) : app[methodName](pathName, authCheck(), controller[propName]); 60 | } 61 | } 62 | }; 63 | 64 | // mount REST routes for all models last so it doesn't override the controller methods 65 | for (let model of mongoose.modelNames()){ 66 | // TODO figure out how to inject middleware that executes beforehand, extend koa-mongo-rest 67 | generateApi(app, mongoose.model(model), '/' + config.app.apiPrefix); 68 | } 69 | }; 70 | 71 | function* responseTime(next) { 72 | var start = new Date; 73 | yield next; 74 | var ms = new Date - start; 75 | this.set('X-Response-Time', ms + 'ms'); 76 | } 77 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-skeleton-navigation", 3 | "version": "0.15.1", 4 | "description": "A starter kit for building a standard navigation-style app with Aurelia.", 5 | "keywords": [ 6 | "aurelia", 7 | "navigation", 8 | "skeleton" 9 | ], 10 | "homepage": "http://aurelia.io", 11 | "bugs": { 12 | "url": "https://github.com/aurelia/skeleton-navigation/issues" 13 | }, 14 | "license": "MIT", 15 | "author": "Rob Eisenberg (http://robeisenberg.com/)", 16 | "main": "dist/commonjs/index.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "http://github.com/aurelia/skeleton-navigation" 20 | }, 21 | "devDependencies": { 22 | "aurelia-tools": "^0.1.3", 23 | "browser-sync": "^1.8.1", 24 | "conventional-changelog": "0.0.11", 25 | "del": "^1.1.0", 26 | "gulp": "^3.8.10", 27 | "gulp-babel": "^5.1.0", 28 | "gulp-bump": "^0.3.1", 29 | "gulp-changed": "^1.1.0", 30 | "gulp-jshint": "^1.9.0", 31 | "gulp-plumber": "^0.6.6", 32 | "gulp-protractor": "^0.0.12", 33 | "gulp-sourcemaps": "^1.3.0", 34 | "gulp-yuidoc": "^0.1.2", 35 | "jasmine-core": "^2.1.3", 36 | "jshint-stylish": "^1.0.0", 37 | "karma": "^0.12.28", 38 | "karma-babel-preprocessor": "^5.2.1", 39 | "karma-chrome-launcher": "^0.1.7", 40 | "karma-coverage": "^0.3.1", 41 | "karma-jasmine": "^0.3.5", 42 | "karma-jspm": "^1.1.5", 43 | "object.assign": "^1.0.3", 44 | "require-dir": "^0.1.0", 45 | "run-sequence": "^1.0.2", 46 | "vinyl-paths": "^1.0.0", 47 | "yargs": "^2.1.1", 48 | "resolve-dep": "^0.5.3", 49 | "deep-extend": "^0.4.0", 50 | "less-plugin-autoprefix": "^1.4.1", 51 | "less-plugin-clean-css": "^1.5.0", 52 | "bootstrap-less": "^3.3.8", 53 | "gulp-less": "^3.0.3", 54 | "gulp-autoprefixer": "^2.3.0", 55 | "gulp-minify-css": "^1.1.1" 56 | }, 57 | "jspm": { 58 | "dependencies": { 59 | "aurelia-animator-css": "github:aurelia/animator-css@^0.13.0", 60 | "aurelia-bootstrapper": "github:aurelia/bootstrapper@^0.14.0", 61 | "aurelia-bs-modal": "github:pwkad/aurelia-bs-modal@^0.2.0", 62 | "aurelia-computed": "github:jdanyow/aurelia-computed@^0.3.0", 63 | "aurelia-dependency-injection": "github:aurelia/dependency-injection@^0.9.0", 64 | "aurelia-framework": "github:aurelia/framework@^0.13.0", 65 | "aurelia-http-client": "github:aurelia/http-client@^0.10.0", 66 | "aurelia-router": "github:aurelia/router@^0.10.0", 67 | "bootstrap": "github:twbs/bootstrap@^3.3.4", 68 | "core-js": "npm:core-js@^0.9.18", 69 | "css": "github:systemjs/plugin-css@^0.1.11", 70 | "font-awesome": "npm:font-awesome@^4.3.0", 71 | "jquery": "github:components/jquery@^2.1.4" 72 | }, 73 | "devDependencies": { 74 | "babel": "npm:babel-core@^5.1.13", 75 | "babel-runtime": "npm:babel-runtime@^5.1.13", 76 | "core-js": "npm:core-js@^0.9.4" 77 | }, 78 | "overrides": { 79 | "npm:core-js@0.9.18": { 80 | "main": "client/shim.min" 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # and sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | 21 | 22 | 23 | ################################################ 24 | # Local Configuration 25 | # 26 | # Explicitly ignore files which contain: 27 | # 28 | # 1. Sensitive information you'd rather not push to 29 | # your git repository. 30 | # e.g., your personal API keys or passwords. 31 | # 32 | # 2. Environment-specific configuration 33 | # Basically, anything that would be annoying 34 | # to have to change every time you do a 35 | # `git pull` 36 | # e.g., your local development database, or 37 | # the S3 bucket you're using for file uploads 38 | # development. 39 | # 40 | ################################################ 41 | 42 | config/local.js 43 | 44 | 45 | 46 | 47 | 48 | ################################################ 49 | # Dependencies 50 | # 51 | # When releasing a production app, you may 52 | # consider including your node_modules and 53 | # bower_components directory in your git repo, 54 | # but during development, its best to exclude it, 55 | # since different developers may be working on 56 | # different kernels, where dependencies would 57 | # need to be recompiled anyway. 58 | # 59 | # More on that here about node_modules dir: 60 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 61 | # (credit Mikeal Rogers, @mikeal) 62 | # 63 | # About bower_components dir, you can see this: 64 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 65 | # (credit Addy Osmani, @addyosmani) 66 | # 67 | ################################################ 68 | 69 | node_modules 70 | bower_components 71 | 72 | 73 | 74 | 75 | ################################################ 76 | # Sails.js / Waterline / Grunt 77 | # 78 | # Files generated by Sails and Grunt, or related 79 | # tasks and adapters. 80 | ################################################ 81 | .tmp 82 | dump.rdb 83 | 84 | 85 | 86 | 87 | 88 | ################################################ 89 | # Node.js / NPM 90 | # 91 | # Common files generated by Node, NPM, and the 92 | # related ecosystem. 93 | ################################################ 94 | lib-cov 95 | *.seed 96 | *.log 97 | *.out 98 | *.pid 99 | npm-debug.log 100 | 101 | 102 | 103 | 104 | 105 | ################################################ 106 | # Miscellaneous 107 | # 108 | # Common files generated by text editors, 109 | # operating systems, file systems, etc. 110 | ################################################ 111 | 112 | *~ 113 | *# 114 | .DS_STORE 115 | .netbeans 116 | nbproject 117 | .idea 118 | .node_history 119 | uploads 120 | assets/images 121 | !assets/images/icons -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/core.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Core: Genral Layout Style 3 | * ------------------------- 4 | */ 5 | html, 6 | body { 7 | min-height: 100%; 8 | .layout-boxed & { 9 | height: 100%; 10 | } 11 | } 12 | 13 | body { 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | font-weight: 400; 18 | overflow-x: hidden; 19 | overflow-y: auto; 20 | } 21 | 22 | /* Layout */ 23 | .wrapper { 24 | .clearfix(); 25 | min-height: 100%; 26 | position: relative; 27 | overflow: hidden!important; 28 | .layout-boxed & { 29 | max-width: 1250px; 30 | margin: 0 auto; 31 | min-height: 100%; 32 | box-shadow: 0 0 8px rgba(0,0,0,0.5); 33 | position: relative; 34 | } 35 | } 36 | 37 | .layout-boxed { 38 | background: url('@{boxed-layout-bg-image-path}') repeat fixed; 39 | } 40 | 41 | /* 42 | * Content Wrapper - contains the main content 43 | * ```.right-side has been deprecated as of v2.0.0 in favor of .content-wrapper ``` 44 | */ 45 | .content-wrapper, 46 | .right-side, 47 | .main-footer { 48 | //Using disposable variable to join statements with a comma 49 | @transition-rule: @transition-speed @transition-fn, 50 | margin @transition-speed @transition-fn; 51 | .transition-transform(@transition-rule); 52 | margin-left: @sidebar-width; 53 | z-index: 820; 54 | //Top nav layout 55 | .layout-top-nav & { 56 | margin-left: 0; 57 | } 58 | @media (max-width: @screen-xs-max) { 59 | margin-left: 0; 60 | } 61 | //When opening the sidebar on large screens 62 | .sidebar-collapse & { 63 | @media (min-width: @screen-sm) { 64 | margin-left: 0; 65 | } 66 | } 67 | //When opening the sidebar on small screens 68 | .sidebar-open & { 69 | @media (max-width: @screen-xs-max) { 70 | .translate(@sidebar-width, 0); 71 | } 72 | } 73 | } 74 | 75 | .content-wrapper, 76 | .right-side { 77 | min-height: 100%; 78 | background-color: @body-bg; 79 | z-index: 800; 80 | } 81 | .main-footer { 82 | background: #fff; 83 | padding: 15px; 84 | color: #444; 85 | border-top: 1px solid @gray; 86 | } 87 | 88 | /* Fixed layout */ 89 | .fixed { 90 | .main-header, 91 | .main-sidebar, 92 | .left-side { 93 | position: fixed; 94 | } 95 | .main-header { 96 | top: 0; 97 | right: 0; 98 | left: 0; 99 | } 100 | .content-wrapper, 101 | .right-side { 102 | padding-top: 50px; 103 | @media (max-width: @screen-header-collapse) { 104 | padding-top: 100px; 105 | } 106 | } 107 | &.layout-boxed { 108 | .wrapper { 109 | max-width: 100%; 110 | } 111 | } 112 | } 113 | 114 | /* Content */ 115 | .content { 116 | min-height: 250px; 117 | padding: 15px; 118 | .container-fixed(@grid-gutter-width); 119 | } 120 | 121 | /* H1 - H6 font */ 122 | h1, 123 | h2, 124 | h3, 125 | h4, 126 | h5, 127 | h6, 128 | .h1, 129 | .h2, 130 | .h3, 131 | .h4, 132 | .h5, 133 | .h6 { 134 | font-family: 'Source Sans Pro', sans-serif; 135 | } 136 | /* General Links */ 137 | a { 138 | color: @link-color; 139 | } 140 | a:hover, 141 | a:active, 142 | a:focus { 143 | outline: none; 144 | text-decoration: none; 145 | color: @link-hover-color; 146 | } 147 | 148 | /* Page Header */ 149 | .page-header { 150 | margin: 10px 0 20px 0; 151 | font-size: 22px; 152 | 153 | > small { 154 | color: #666; 155 | display: block; 156 | margin-top: 5px; 157 | } 158 | } -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-right/side-bar-right.js: -------------------------------------------------------------------------------- 1 | export class RightSideBar { 2 | 3 | attached() { 4 | /* ControlSidebar 5 | * ============== 6 | * Adds functionality to the right sidebar 7 | * 8 | * @type Object 9 | * @usage $.AdminLTE.controlSidebar.activate(options) 10 | */ 11 | var o = $.AdminLTE.options; 12 | //Enable control sidebar 13 | 14 | 15 | $.AdminLTE.controlSidebar = { 16 | //instantiate the object 17 | activate: function () { 18 | //Get the object 19 | var _this = this; 20 | //Update options 21 | var o = $.AdminLTE.options.controlSidebarOptions; 22 | //Get the sidebar 23 | var sidebar = $(o.selector); 24 | //The toggle button 25 | var btn = $(o.toggleBtnSelector); 26 | 27 | //Listen to the click event 28 | btn.on('click', function (e) { 29 | e.preventDefault(); 30 | //If the sidebar is not open 31 | if (!sidebar.hasClass('control-sidebar-open') && !$('body').hasClass('control-sidebar-open')) { 32 | //Open the sidebar 33 | _this.open(sidebar, o.slide); 34 | } else { 35 | _this.close(sidebar, o.slide); 36 | } 37 | }); 38 | 39 | //If the body has a boxed layout, fix the sidebar bg position 40 | var bg = $(".control-sidebar-bg"); 41 | _this._fix(bg); 42 | 43 | //If the body has a fixed layout, make the control sidebar fixed 44 | if ($('body').hasClass('fixed')) { 45 | _this._fixForFixed(sidebar); 46 | } else { 47 | //If the content height is less than the sidebar's height, force max height 48 | if ($('.content-wrapper, .right-side').height() < sidebar.height()) { 49 | _this._fixForContent(sidebar); 50 | } 51 | } 52 | }, 53 | //Open the control sidebar 54 | open: function (sidebar, slide) { 55 | var _this = this; 56 | //Slide over content 57 | if (slide) { 58 | sidebar.addClass('control-sidebar-open'); 59 | } else { 60 | //Push the content by adding the open class to the body instead 61 | //of the sidebar itself 62 | $('body').addClass('control-sidebar-open'); 63 | } 64 | }, 65 | //Close the control sidebar 66 | close: function (sidebar, slide) { 67 | if (slide) { 68 | sidebar.removeClass('control-sidebar-open'); 69 | } else { 70 | $('body').removeClass('control-sidebar-open'); 71 | } 72 | }, 73 | _fix: function (sidebar) { 74 | var _this = this; 75 | if ($("body").hasClass('layout-boxed')) { 76 | sidebar.css('position', 'absolute'); 77 | sidebar.height($(".wrapper").height()); 78 | $(window).resize(function () { 79 | _this._fix(sidebar); 80 | }); 81 | } else { 82 | sidebar.css({ 83 | 'position': 'fixed', 84 | 'height': 'auto' 85 | }); 86 | } 87 | }, 88 | _fixForFixed: function (sidebar) { 89 | sidebar.css({ 90 | 'position': 'fixed', 91 | 'max-height': '100%', 92 | 'overflow': 'auto', 93 | 'padding-bottom': '50px' 94 | }); 95 | }, 96 | _fixForContent: function (sidebar) { 97 | $(".content-wrapper, .right-side").css('min-height', sidebar.height()); 98 | } 99 | }; 100 | 101 | if (o.enableControlSidebar) { 102 | $.AdminLTE.controlSidebar.activate(); 103 | } 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /client/src/utils/layout/config.js: -------------------------------------------------------------------------------- 1 | 2 | /* AdminLTE 3 | * 4 | * @type Object 5 | * @description $.AdminLTE is the main object for the template's app. 6 | * It's used for implementing functions and options related 7 | * to the template. Keeping everything wrapped in an object 8 | * prevents conflict with other plugins and is a better 9 | * way to organize our code. 10 | */ 11 | $.AdminLTE = {}; 12 | 13 | /* -------------------- 14 | * - AdminLTE Options - 15 | * -------------------- 16 | * Modify these options to suit your implementation 17 | */ 18 | $.AdminLTE.options = { 19 | //Add slimscroll to navbar menus 20 | //This requires you to load the slimscroll plugin 21 | //in every page before app.js 22 | navbarMenuSlimscroll: true, 23 | navbarMenuSlimscrollWidth: "3px", //The width of the scroll bar 24 | navbarMenuHeight: "200px", //The height of the inner menu 25 | //Sidebar push menu toggle button selector 26 | sidebarToggleSelector: "[data-toggle='offcanvas']", 27 | //Activate sidebar push menu 28 | sidebarPushMenu: true, 29 | //Activate sidebar slimscroll if the fixed layout is set (requires SlimScroll Plugin) 30 | sidebarSlimScroll: true, 31 | //Enable sidebar expand on hover effect for sidebar mini 32 | //This option is forced to true if both the fixed layout and sidebar mini 33 | //are used together 34 | sidebarExpandOnHover: false, 35 | //BoxRefresh Plugin 36 | enableBoxRefresh: true, 37 | //Bootstrap.js tooltip 38 | enableBSToppltip: true, 39 | BSTooltipSelector: "[data-toggle='tooltip']", 40 | //Enable Fast Click. Fastclick.js creates a more 41 | //native touch experience with touch devices. If you 42 | //choose to enable the plugin, make sure you load the script 43 | //before AdminLTE's app.js 44 | enableFastclick: true, 45 | //Control Sidebar Options 46 | enableControlSidebar: true, 47 | controlSidebarOptions: { 48 | //Which button should trigger the open/close event 49 | toggleBtnSelector: "[data-toggle='control-sidebar']", 50 | //The sidebar selector 51 | selector: ".control-sidebar", 52 | //Enable slide over content 53 | slide: true 54 | }, 55 | //Box Widget Plugin. Enable this plugin 56 | //to allow boxes to be collapsed and/or removed 57 | enableBoxWidget: true, 58 | //Box Widget plugin options 59 | boxWidgetOptions: { 60 | boxWidgetIcons: { 61 | //Collapse icon 62 | collapse: 'fa-minus', 63 | //Open icon 64 | open: 'fa-plus', 65 | //Remove icon 66 | remove: 'fa-times' 67 | }, 68 | boxWidgetSelectors: { 69 | //Remove button selector 70 | remove: '[data-widget="remove"]', 71 | //Collapse button selector 72 | collapse: '[data-widget="collapse"]' 73 | } 74 | }, 75 | //Direct Chat plugin options 76 | directChat: { 77 | //Enable direct chat by default 78 | enable: true, 79 | //The button to open and close the chat contacts pane 80 | contactToggleSelector: '[data-widget="chat-pane-toggle"]' 81 | }, 82 | //Define the set of colors to use globally around the website 83 | colors: { 84 | lightBlue: "#3c8dbc", 85 | red: "#f56954", 86 | green: "#00a65a", 87 | aqua: "#00c0ef", 88 | yellow: "#f39c12", 89 | blue: "#0073b7", 90 | navy: "#001F3F", 91 | teal: "#39CCCC", 92 | olive: "#3D9970", 93 | lime: "#01FF70", 94 | orange: "#FF851B", 95 | fuchsia: "#F012BE", 96 | purple: "#8E24AA", 97 | maroon: "#D81B60", 98 | black: "#222222", 99 | gray: "#d2d6de" 100 | }, 101 | //The standard screen sizes that bootstrap uses. 102 | //If you change these in the variables.less file, change 103 | //them here too. 104 | screenSizes: { 105 | xs: 480, 106 | sm: 768, 107 | md: 992, 108 | lg: 1200 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/buttons.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Button 3 | * ----------------- 4 | */ 5 | 6 | .btn { 7 | .border-radius(@btn-border-radius); 8 | .box-shadow(@btn-boxshadow); 9 | border: 1px solid transparent; 10 | 11 | &.uppercase { 12 | text-transform: uppercase 13 | } 14 | 15 | // Flat buttons 16 | &.btn-flat { 17 | .border-radius(0); 18 | -webkit-box-shadow: none; 19 | -moz-box-shadow: none; 20 | box-shadow: none; 21 | border-width: 1px; 22 | } 23 | 24 | // Active state 25 | &:active { 26 | -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 27 | -moz-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 28 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 29 | } 30 | 31 | &:focus { 32 | outline: none; 33 | } 34 | 35 | // input file btn 36 | &.btn-file { 37 | position: relative; 38 | overflow: hidden; 39 | > input[type='file'] { 40 | position: absolute; 41 | top: 0; 42 | right: 0; 43 | min-width: 100%; 44 | min-height: 100%; 45 | font-size: 100px; 46 | text-align: right; 47 | .opacity(0); 48 | outline: none; 49 | background: white; 50 | cursor: inherit; 51 | display: block; 52 | } 53 | } 54 | } 55 | 56 | //Button color variations 57 | .btn-default { 58 | background-color: #f4f4f4; 59 | color: #444; 60 | border-color: #ddd; 61 | &:hover, &:active, &.hover { 62 | background-color:darken(#f4f4f4, 5%)!important; 63 | } 64 | } 65 | .btn-primary { 66 | background-color: @light-blue; 67 | border-color: darken(@light-blue, 5%); 68 | &:hover, &:active, &.hover { 69 | background-color: darken(@light-blue, 5%); 70 | } 71 | } 72 | .btn-success { 73 | background-color: @green; 74 | border-color: darken(@green, 5%); 75 | &:hover, &:active, &.hover { 76 | background-color: darken(@green, 5%); 77 | } 78 | } 79 | .btn-info { 80 | background-color: @aqua; 81 | border-color: darken(@aqua, 5%); 82 | &:hover, &:active, &.hover { 83 | background-color: darken(@aqua, 5%); 84 | } 85 | } 86 | .btn-danger { 87 | background-color: @red; 88 | border-color: darken(@red, 5%); 89 | &:hover, &:active, &.hover { 90 | background-color: darken(@red, 5%); 91 | } 92 | } 93 | .btn-warning { 94 | background-color: @yellow; 95 | border-color: darken(@yellow, 5%); 96 | &:hover, &:active, &.hover { 97 | background-color: darken(@yellow, 5%); 98 | } 99 | } 100 | .btn-outline { 101 | border: 1px solid #fff; 102 | background: transparent; 103 | color: #fff; 104 | &:hover, 105 | &:focus, 106 | &:active { 107 | color: rgba(255,255,255,.7); 108 | border-color: rgba(255,255,255,.7); 109 | } 110 | } 111 | .btn-link { 112 | .box-shadow(none); 113 | } 114 | //General .btn with bg class 115 | .btn[class*='bg-']:hover { 116 | .box-shadow(inset 0 0 100px rgba(0,0,0,0.2)); 117 | } 118 | // Application buttons 119 | .btn-app { 120 | .border-radius(3px); 121 | position: relative; 122 | padding: 15px 5px; 123 | margin: 0 0 10px 10px; 124 | min-width: 80px; 125 | height: 60px; 126 | text-align: center; 127 | color: #666; 128 | border: 1px solid #ddd; 129 | background-color: #f4f4f4; 130 | font-size: 12px; 131 | //Icons within the btn 132 | > .fa, > .glyphicon, > .ion { 133 | font-size: 20px; 134 | display: block; 135 | } 136 | 137 | &:hover { 138 | background: #f4f4f4; 139 | color: #444; 140 | border-color: #aaa; 141 | } 142 | 143 | &:active, &:focus { 144 | -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 145 | -moz-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 146 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 147 | } 148 | 149 | //The badge 150 | > .badge { 151 | position: absolute; 152 | top: -3px; 153 | right: -10px; 154 | font-size: 10px; 155 | font-weight: 400; 156 | } 157 | } -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/navs.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Nav 3 | * -------------- 4 | */ 5 | 6 | .nav { 7 | > li > a:hover, 8 | > li > a:active, 9 | > li > a:focus { 10 | color: #444; 11 | background: #f7f7f7; 12 | } 13 | } 14 | 15 | /* NAV PILLS */ 16 | .nav-pills { 17 | > li > a { 18 | .border-radius(0); 19 | border-top: 3px solid transparent; 20 | color: #444; 21 | > .fa, 22 | > .glyphicon, 23 | > .ion { 24 | margin-right: 5px; 25 | } 26 | } 27 | > li.active > a, 28 | > li.active > a:hover, 29 | > li.active > a:focus { 30 | border-top-color: @light-blue; 31 | } 32 | > li.active > a { 33 | font-weight: 600; 34 | } 35 | } 36 | /* NAV STACKED */ 37 | .nav-stacked { 38 | > li > a { 39 | .border-radius(0); 40 | border-top: 0; 41 | border-left: 3px solid transparent; 42 | color: #444; 43 | } 44 | > li.active > a, 45 | > li.active > a:hover { 46 | border-top: 0; 47 | border-left-color: @light-blue; 48 | } 49 | 50 | > li.header { 51 | border-bottom: 1px solid #ddd; 52 | color: #777; 53 | margin-bottom: 10px; 54 | padding: 5px 10px; 55 | text-transform: uppercase; 56 | } 57 | } 58 | 59 | /* NAV TABS */ 60 | .nav-tabs-custom { 61 | margin-bottom: 20px; 62 | background: #fff; 63 | box-shadow: @box-boxshadow; 64 | border-radius: @box-border-radius; 65 | > .nav-tabs { 66 | margin: 0; 67 | border-bottom-color: #f4f4f4; 68 | .border-top-radius(@box-border-radius); 69 | > li { 70 | border-top: 3px solid transparent; 71 | margin-bottom: -2px; 72 | > a { 73 | color: #444; 74 | .border-radius(0)!important; 75 | &, 76 | &:hover { 77 | background: transparent; 78 | margin: 0; 79 | } 80 | &:hover { 81 | color: #999; 82 | } 83 | } 84 | &:not(.active) { 85 | > a:hover, 86 | > a:focus, 87 | > a:active { 88 | border-color: transparent; 89 | } 90 | } 91 | margin-right: 5px; 92 | } 93 | 94 | > li.active { 95 | border-top-color: @light-blue; 96 | & > a, 97 | &:hover > a { 98 | background-color: #fff; 99 | color: #444; 100 | } 101 | > a { 102 | border-top-color: transparent; 103 | border-left-color: #f4f4f4; 104 | border-right-color: #f4f4f4; 105 | } 106 | 107 | } 108 | 109 | > li:first-of-type { 110 | margin-left: 0; 111 | &.active { 112 | > a { 113 | border-left-color: transparent; 114 | } 115 | } 116 | } 117 | 118 | //Pulled to the right 119 | &.pull-right { 120 | float: none!important; 121 | > li { 122 | float: right; 123 | } 124 | > li:first-of-type { 125 | margin-right: 0; 126 | > a { 127 | border-left-width: 1px; 128 | } 129 | &.active { 130 | > a { 131 | border-left-color: #f4f4f4; 132 | border-right-color: transparent; 133 | } 134 | } 135 | } 136 | } 137 | 138 | > li.header { 139 | line-height: 35px; 140 | padding: 0 10px; 141 | font-size: 20px; 142 | color: #444; 143 | > .fa, 144 | > .glyphicon, 145 | > .ion { 146 | margin-right: 5px; 147 | } 148 | } 149 | } 150 | 151 | > .tab-content { 152 | background: #fff; 153 | padding: 10px; 154 | .border-bottom-radius(@box-border-radius); 155 | } 156 | 157 | .dropdown.open > a { 158 | &:active, 159 | &:focus { 160 | background: transparent; 161 | color: #999; 162 | } 163 | } 164 | } 165 | 166 | /* PAGINATION */ 167 | .pagination { 168 | > li > a { 169 | background: #fafafa; 170 | color: #666; 171 | .border-radius(0)!important; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /client/src/components/layout-admin/footer-main/footer-main.js: -------------------------------------------------------------------------------- 1 | import 'utils/layout/config'; 2 | 3 | export class FooterMain { 4 | attached() { 5 | 6 | //Extend options if external options exist 7 | if (typeof AdminLTEOptions !== "undefined") { 8 | $.extend(true, 9 | $.AdminLTE.options, 10 | AdminLTEOptions); 11 | } 12 | 13 | //Set up the object 14 | _init(); 15 | 16 | //Activate the layout maker 17 | $.AdminLTE.layout.activate(); 18 | 19 | 20 | /* ---------------------------------- 21 | * - Initialize the AdminLTE Object - 22 | * ---------------------------------- 23 | * All AdminLTE functions are implemented below. 24 | */ 25 | function _init() { 26 | 27 | /* Layout 28 | * ====== 29 | * Fixes the layout height in case min-height fails. 30 | * 31 | * @type Object 32 | * @usage $.AdminLTE.layout.activate() 33 | * $.AdminLTE.layout.fix() 34 | * $.AdminLTE.layout.fixSidebar() 35 | */ 36 | $.AdminLTE.layout = { 37 | activate: function () { 38 | var _this = this; 39 | _this.fix(); 40 | _this.fixSidebar(); 41 | $(window, ".wrapper").resize(function () { 42 | _this.fix(); 43 | _this.fixSidebar(); 44 | }); 45 | }, 46 | fix: function () { 47 | //Get window height and the wrapper height 48 | var neg = $('.main-header').outerHeight() + $('.main-footer').outerHeight(); 49 | var window_height = $(window).height(); 50 | var sidebar_height = $(".sidebar").height(); 51 | //Set the min-height of the content and sidebar based on the 52 | //the height of the document. 53 | if ($("body").hasClass("fixed")) { 54 | $(".content-wrapper, .right-side").css('min-height', window_height - $('.main-footer').outerHeight()); 55 | } else { 56 | var postSetWidth; 57 | if (window_height >= sidebar_height) { 58 | $(".content-wrapper, .right-side").css('min-height', window_height - neg); 59 | postSetWidth = window_height - neg; 60 | } else { 61 | $(".content-wrapper, .right-side").css('min-height', sidebar_height); 62 | postSetWidth = sidebar_height; 63 | } 64 | 65 | //Fix for the control sidebar height 66 | var controlSidebar = $($.AdminLTE.options.controlSidebarOptions.selector); 67 | if (typeof controlSidebar !== "undefined") { 68 | if (controlSidebar.height() > postSetWidth) 69 | $(".content-wrapper, .right-side").css('min-height', controlSidebar.height()); 70 | } 71 | 72 | } 73 | }, 74 | fixSidebar: function () { 75 | //Make sure the body tag has the .fixed class 76 | if (!$("body").hasClass("fixed")) { 77 | if (typeof $.fn.slimScroll != 'undefined') { 78 | $(".sidebar").slimScroll({ 79 | destroy: true 80 | }).height("auto"); 81 | } 82 | return; 83 | } else if (typeof $.fn.slimScroll == 'undefined' && console) { 84 | console.error("Error: the fixed layout requires the slimscroll plugin!"); 85 | } 86 | //Enable slimscroll for fixed layout 87 | if ($.AdminLTE.options.sidebarSlimScroll) { 88 | if (typeof $.fn.slimScroll != 'undefined') { 89 | //Destroy if it exists 90 | $(".sidebar").slimScroll({ 91 | destroy: true 92 | }).height("auto"); 93 | //Add slimscroll 94 | $(".sidebar").slimscroll({ 95 | height: ($(window).height() - $(".main-header").height()) + "px", 96 | color: "rgba(0,0,0,0.2)", 97 | size: "3px" 98 | }); 99 | } 100 | } 101 | } 102 | }; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/variables.less: -------------------------------------------------------------------------------- 1 | //AdminLTE 2 Variables.less 2 | //========================= 3 | 4 | //PATHS 5 | //-------------------------------------------------------- 6 | 7 | @boxed-layout-bg-image-path: "../img/boxed-bg.jpg"; 8 | 9 | //COLORS 10 | //-------------------------------------------------------- 11 | 12 | @light-blue: #3c8dbc; //Primary 13 | @red: #dd4b39; //Danger 14 | @green: #00a65a; //Success 15 | @aqua: #00c0ef; //Info 16 | @yellow: #f39c12; //Warning 17 | @blue: #0073b7; 18 | @navy: #001F3F; 19 | @teal: #39CCCC; 20 | @olive: #3D9970; 21 | @lime: #01FF70; 22 | @orange: #FF851B; 23 | @fuchsia: #F012BE; 24 | @purple: #605ca8; 25 | @maroon: #D81B60; 26 | @black: #111; 27 | @gray: #d2d6de; 28 | 29 | 30 | // FORMS 31 | 32 | @input-color: #666; 33 | 34 | //LAYOUT 35 | //-------------------------------------------------------- 36 | 37 | //Side bar and logo width 38 | @sidebar-width: 230px; 39 | //Boxed layout maximum width 40 | @boxed-layout-max-width: 1024px; 41 | //When the logo should go to the top of the screen 42 | @screen-header-collapse: @screen-xs-max; 43 | 44 | //Link colors (Aka: tags) 45 | @link-color: @light-blue; 46 | @link-hover-color: lighten(@link-color, 15%); 47 | 48 | //Body background (Affects main content background only) 49 | @body-bg: #ecf0f5; 50 | 51 | //SIDEBAR SKINS 52 | //-------------------------------------------------------- 53 | 54 | //Dark sidebar 55 | @sidebar-dark-bg: #222d32; 56 | @sidebar-dark-hover-bg: darken(@sidebar-dark-bg, 2%); 57 | @sidebar-dark-color: lighten(@sidebar-dark-bg, 60%); 58 | @sidebar-dark-hover-color: #fff; 59 | @sidebar-dark-submenu-bg: lighten(@sidebar-dark-bg, 5%); 60 | @sidebar-dark-submenu-color: lighten(@sidebar-dark-submenu-bg, 40%); 61 | @sidebar-dark-submenu-hover-color: #fff; 62 | 63 | //Light sidebar 64 | @sidebar-light-bg: #f9fafc; 65 | @sidebar-light-hover-bg: lighten(#f0f0f1, 1.5%); 66 | @sidebar-light-color: #444; 67 | @sidebar-light-hover-color: #000; 68 | @sidebar-light-submenu-bg: @sidebar-light-hover-bg; 69 | @sidebar-light-submenu-color: #777; 70 | @sidebar-light-submenu-hover-color: #000; 71 | 72 | //CONTROL SIDEBAR 73 | //-------------------------------------------------------- 74 | @control-sidebar-width: @sidebar-width; 75 | 76 | 77 | //BOXES 78 | //-------------------------------------------------------- 79 | @box-border-color: #f4f4f4; 80 | @box-border-radius: 3px; 81 | @box-footer-bg: #fff; 82 | @box-boxshadow: 0 1px 1px rgba(0, 0, 0, .1); 83 | @box-padding: 10px; 84 | 85 | //Box variants 86 | @box-default-border-top-color: #d2d6de; 87 | 88 | //BUTTONS 89 | //-------------------------------------------------------- 90 | @btn-boxshadow: none; 91 | 92 | //PROGRESS BARS 93 | //-------------------------------------------------------- 94 | @progress-bar-border-radius: 1px; 95 | @progress-bar-sm-border-radius: 1px; 96 | @progress-bar-xs-border-radius: 1px; 97 | 98 | //FORMS 99 | //-------------------------------------------------------- 100 | @input-radius: 0px; 101 | 102 | //BUTTONS 103 | //-------------------------------------------------------- 104 | 105 | //Border radius for non flat buttons 106 | @btn-border-radius: 3px; 107 | 108 | //DIRECT CHAT 109 | //-------------------------------------------------------- 110 | @direct-chat-height: 250px; 111 | @direct-chat-default-msg-bg: @gray; 112 | @direct-chat-default-font-color: #444; 113 | @direct-chat-default-msg-border-color: @gray; 114 | 115 | 116 | //CHAT WIDGET 117 | //-------------------------------------------------------- 118 | @attachment-border-radius: 3px; 119 | 120 | //TRANSITIONS SETTINGS 121 | //-------------------------------------------------------- 122 | 123 | //Transition global options 124 | @transition-speed: .3s; 125 | @transition-fn: ease-in-out;//cubic-bezier(0.32,1.25,0.375,1.15); 126 | -------------------------------------------------------------------------------- /client/src/services/geo-google.js: -------------------------------------------------------------------------------- 1 | import {inject} from 'aurelia-framework'; 2 | import {HttpClient} from 'aurelia-http-client'; 3 | import {point} from '../utils/to-geo-json'; 4 | import {EventAggregator} from 'aurelia-event-aggregator'; 5 | 6 | @inject(HttpClient, EventAggregator) 7 | class GeoGoogleService { 8 | 9 | constructor(http, eventAggregator) { 10 | this.http = http; 11 | this.eventAggregator = eventAggregator; 12 | } 13 | 14 | placeListingClick(marker) { 15 | google.maps.event.trigger(marker, 'click'); 16 | } 17 | 18 | async getGeoposition() { 19 | return new Promise((resolve, reject) => { 20 | let success = pos => { 21 | // TODO enable real geo eventually 22 | // resolve(pos.coords) 23 | resolve({latitude: 47.6268381,longitude: -122.3618504}); 24 | }; 25 | success(); 26 | let error = err => console.warn(`ERROR(${err.code}): ${err.message}`); 27 | // navigator.geolocation.getCurrentPosition(success, error); 28 | }); 29 | } 30 | 31 | async geocoding(lookup, reverse) { 32 | let url = reverse ? `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lookup.latitude},${lookup.longitude}` : `https://maps.googleapis.com/maps/api/geocode/json?address=${lookup}`; 33 | return this.http.post(url); 34 | } 35 | 36 | async getCurrentAddress() { 37 | let geo = await this.getGeoposition(); 38 | return await this.getAddressForLatLong(geo); 39 | } 40 | 41 | async getLatLongForAddress(address) { 42 | let location = await this.geocoding(address); 43 | return point(location.content.results[0].geometry.location); 44 | } 45 | 46 | async getAddressForLatLong(latlong) { 47 | let geo = await this.geocoding(latlong, true); 48 | return geo.content.results[0].formatted_address; 49 | } 50 | 51 | clearMarkers() { 52 | for (var i = 0; i < window.markers.length; i++) { 53 | window.markers[i].setMap(null); 54 | } 55 | } 56 | 57 | getNearbyPlaces(options) { 58 | 59 | let request = { 60 | location: this.getGoogleMapsGeoCoords(options.geo), 61 | radius: options.radius, 62 | query: options.query 63 | }; 64 | 65 | var service = new google.maps.places.PlacesService(window.map); 66 | 67 | let createMarker = (place, index) => { 68 | let icon = { 69 | url: place.icon, 70 | scaledSize: new google.maps.Size(20, 20) 71 | }; 72 | let marker = new google.maps.Marker({ 73 | map: window.map, 74 | position: place.geometry.location, 75 | icon: icon 76 | }); 77 | 78 | window.markers.push(marker); 79 | 80 | google.maps.event.addListener(marker, 'click', () => { 81 | this.eventAggregator.publish('googleMaps:markerClick', index); 82 | 83 | window.infoWindow.setContent(place.name); 84 | window.infoWindow.open(window.map, marker); 85 | }); 86 | } 87 | 88 | return new Promise((resolve) => { 89 | service.textSearch(request, (places, status) => { 90 | if (status === google.maps.places.PlacesServiceStatus.OK && options.pinMarkers) { 91 | for (var i = 0; i < places.length; i++) { 92 | createMarker(places[i], i); 93 | } 94 | } 95 | 96 | // let markerCluster = new MarkerClusterer(window.map, window.markers); 97 | resolve(places); 98 | }); 99 | }); 100 | } 101 | 102 | drawMap(options) { 103 | let center = this.getGoogleMapsGeoCoords(options.geo); 104 | window.infoWindow = new google.maps.InfoWindow(); 105 | window.map = new google.maps.Map(document.getElementById(options.mapElementSelector), { 106 | center: center, 107 | zoom: 10, 108 | mapTypeId: google.maps.MapTypeId[options.type] 109 | }); 110 | 111 | let icon = { 112 | url: 'images/icons/ninja.svg', 113 | scaledSize: new google.maps.Size(65, 65) 114 | }; 115 | 116 | if (options.pinCenter) { 117 | new google.maps.Marker({ 118 | map: window.map, 119 | position: center, 120 | icon: icon 121 | }); 122 | } 123 | } 124 | 125 | getGoogleMapsGeoCoords(geo) { 126 | return new google.maps.LatLng(geo.latitude, geo.longitude); 127 | } 128 | } 129 | 130 | export { 131 | GeoGoogleService 132 | } 133 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # aurelia-skeleton-navigation 2 | 3 | [![ZenHub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) 4 | [![Join the chat at https://gitter.im/aurelia/discuss](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/aurelia/discuss?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | This skeleton is part of the [Aurelia](http://www.aurelia.io/) platform. It sets up a standard navigation-style app using gulp to build your ES6 code with the Babel compiler. Karma/Protractor/Jasmine testing is also configured. 7 | 8 | > To keep up to date on [Aurelia](http://www.aurelia.io/), please visit and subscribe to [the official blog](http://blog.durandal.io/). If you have questions, we invite you to [join us on Gitter](https://gitter.im/aurelia/discuss). If you would like to have deeper insight into our development process, please install the [ZenHub](https://zenhub.io) Chrome Extension and visit any of our repository's boards. You can get an overview of all Aurelia work by visiting [the framework board](https://github.com/aurelia/framework#boards). 9 | 10 | ## Running The App 11 | 12 | To run the app, follow these steps. 13 | 14 | 1. Ensure that [NodeJS](http://nodejs.org/) is installed. This provides the platform on which the build tooling runs. 15 | 2. From the project folder, execute the following command: 16 | 17 | ```shell 18 | npm install 19 | ``` 20 | 3. Ensure that [Gulp](http://gulpjs.com/) is installed. If you need to install it, use the following command: 21 | 22 | ```shell 23 | npm install -g gulp 24 | ``` 25 | 4. Ensure that [jspm](http://jspm.io/) is installed. If you need to install it, use the following command: 26 | 27 | ```shell 28 | npm install -g jspm 29 | ``` 30 | > **Note:** jspm queries GitHub to install semver packages, but GitHub has a rate limit on anonymous API requests. It is advised that you configure jspm with your GitHub credentials in order to avoid problems. You can do this by executing `jspm registry config github` and following the prompts. 31 | 5. Install the client-side dependencies with jspm: 32 | 33 | ```shell 34 | jspm install -y 35 | ``` 36 | >**Note:** Windows users, if you experience an error of "unknown command unzip" you can solve this problem by doing `npm install -g unzip` and then re-running `jspm install`. 37 | 6. To run the app, execute the following command: 38 | 39 | ```shell 40 | gulp watch 41 | ``` 42 | 7. Browse to [http://localhost:9000](http://localhost:9000) to see the app. You can make changes in the code found under `src` and the browser should auto-refresh itself as you save files. 43 | 44 | > Note: At present there is a bug in the HTMLImports polyfill which only occurs on IE. We have submitted a pull request to the team with the fix. In the mean time, if you want to test on IE, you can work around the issue by explicitly adding a script tag before you load system.js. The script tag should look something like this (be sure to confirm the version number): 45 | 46 | ```html 47 | 48 | ``` 49 | 50 | ## Running The Unit Tests 51 | 52 | To run the unit tests, first ensure that you have followed the steps above in order to install all dependencies and successfully build the library. Once you have done that, proceed with these additional steps: 53 | 54 | 1. Ensure that the [Karma](http://karma-runner.github.io/) CLI is installed. If you need to install it, use the following command: 55 | 56 | ```shell 57 | npm install -g karma-cli 58 | ``` 59 | 2. Install Aurelia libs for test visibility: 60 | 61 | ```shell 62 | jspm install aurelia-framework 63 | jspm install aurelia-http-client 64 | jspm install aurelia-router 65 | ``` 66 | 3. You can now run the tests with this command: 67 | 68 | ```shell 69 | karma start 70 | ``` 71 | 72 | ## Running The E2E Tests 73 | Integration tests are performed with [Protractor](http://angular.github.io/protractor/#/). 74 | 75 | 1. Place your E2E-Tests into the folder ```test/e2e/src``` 76 | 2. Install the necessary webdriver 77 | 78 | ```shell 79 | gulp webdriver_update 80 | ``` 81 | 82 | 3. Configure the path to the webdriver by opening the file ```protractor.conf.js``` and adjusting the ```seleniumServerJar``` property. Typically its only needed to adjust the version number. 83 | 84 | 4. Make sure your app runs and is accessible 85 | 86 | ```shell 87 | gulp watch 88 | ``` 89 | 90 | 5. In another console run the E2E-Tests 91 | 92 | ```shell 93 | gulp e2e 94 | ``` 95 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-left/side-bar-left.js: -------------------------------------------------------------------------------- 1 | import {bindable} from 'aurelia-framework'; 2 | 3 | export class LeftSideBar { 4 | @bindable router = null; 5 | 6 | attached() { 7 | 8 | /* Tree() 9 | * ====== 10 | * Converts the sidebar into a multilevel 11 | * tree view menu. 12 | * 13 | * @type Function 14 | * @Usage: $.AdminLTE.tree('.sidebar') 15 | */ 16 | var o = $.AdminLTE.options; 17 | //Enable sidebar tree view controls 18 | 19 | /* 20 | * INITIALIZE BUTTON TOGGLE 21 | * ------------------------ 22 | */ 23 | $('.btn-group[data-toggle="btn-toggle"]').each(function () { 24 | var group = $(this); 25 | $(this).find(".btn").on('click', function (e) { 26 | group.find(".btn.active").removeClass("active"); 27 | $(this).addClass("active"); 28 | e.preventDefault(); 29 | }); 30 | }); 31 | 32 | /* PushMenu() 33 | * ========== 34 | * Adds the push menu functionality to the sidebar. 35 | * 36 | * @type Function 37 | * @usage: $.AdminLTE.pushMenu("[data-toggle='offcanvas']") 38 | */ 39 | $.AdminLTE.pushMenu = { 40 | activate: function (toggleBtn) { 41 | //Get the screen sizes 42 | var screenSizes = $.AdminLTE.options.screenSizes; 43 | //Enable sidebar toggle 44 | $(toggleBtn).on('click', function (e) { 45 | e.preventDefault(); 46 | //Enable sidebar push menu 47 | if ($(window).width() > (screenSizes.sm - 1)) { 48 | $("body").toggleClass('sidebar-collapse'); 49 | } 50 | //Handle sidebar push menu for small screens 51 | else { 52 | if ($("body").hasClass('sidebar-open')) { 53 | $("body").removeClass('sidebar-open'); 54 | $("body").removeClass('sidebar-collapse') 55 | } else { 56 | $("body").addClass('sidebar-open'); 57 | } 58 | } 59 | }); 60 | 61 | $(".content-wrapper").click(function () { 62 | //Enable hide menu when clicking on the content-wrapper on small screens 63 | if ($(window).width() <= (screenSizes.sm - 1) && $("body").hasClass("sidebar-open")) { 64 | $("body").removeClass('sidebar-open'); 65 | } 66 | }); 67 | 68 | //Enable expand on hover for sidebar mini 69 | if ($.AdminLTE.options.sidebarExpandOnHover || ($('body').hasClass('fixed') && $('body').hasClass('sidebar-mini'))) { 70 | this.expandOnHover(); 71 | } 72 | 73 | }, 74 | expandOnHover: function () { 75 | var _this = this; 76 | var screenWidth = $.AdminLTE.options.screenSizes.sm - 1; 77 | //Expand sidebar on hover 78 | $('.main-sidebar').hover(function () { 79 | if ($('body').hasClass('sidebar-mini') && $("body").hasClass('sidebar-collapse') && $(window).width() > screenWidth) { 80 | _this.expand(); 81 | } 82 | }, function () { 83 | if ($('body').hasClass('sidebar-mini') && $('body').hasClass('sidebar-expanded-on-hover') && $(window).width() > screenWidth) { 84 | _this.collapse(); 85 | } 86 | }); 87 | }, 88 | expand: function () { 89 | $("body").removeClass('sidebar-collapse').addClass('sidebar-expanded-on-hover'); 90 | }, 91 | collapse: function () { 92 | if ($('body').hasClass('sidebar-expanded-on-hover')) { 93 | $('body').removeClass('sidebar-expanded-on-hover').addClass('sidebar-collapse'); 94 | } 95 | } 96 | }; 97 | 98 | $.AdminLTE.tree = function (menu) { 99 | var _this = this; 100 | 101 | $("li a", $(menu)).on('click', function (e) { 102 | //Get the clicked link and the next element 103 | var $this = $(this); 104 | var checkElement = $this.next(); 105 | 106 | //Check if the next element is a menu and is visible 107 | if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible'))) { 108 | //Close the menu 109 | checkElement.slideUp('normal', function () { 110 | checkElement.removeClass('menu-open'); 111 | //Fix the layout in case the sidebar stretches over the height of the window 112 | //_this.layout.fix(); 113 | }); 114 | checkElement.parent("li").removeClass("active"); 115 | } 116 | //If the menu is not visible 117 | else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) { 118 | //Get the parent menu 119 | var parent = $this.parents('ul').first(); 120 | //Close all open menus within the parent 121 | var ul = parent.find('ul:visible').slideUp('normal'); 122 | //Remove the menu-open class from the parent 123 | ul.removeClass('menu-open'); 124 | //Get the parent li 125 | var parent_li = $this.parent("li"); 126 | 127 | //Open the target menu and add the menu-open class 128 | checkElement.slideDown('normal', function () { 129 | //Add the class active to the parent li 130 | checkElement.addClass('menu-open'); 131 | parent.find('li.active').removeClass('active'); 132 | parent_li.addClass('active'); 133 | //Fix the layout in case the sidebar stretches over the height of the window 134 | _this.layout.fix(); 135 | }); 136 | } 137 | //if this isn't a link, prevent the page from being redirected 138 | if (checkElement.is('.treeview-menu')) { 139 | e.preventDefault(); 140 | } 141 | }); 142 | }; 143 | 144 | $.AdminLTE.tree('.sidebar'); 145 | 146 | //Activate sidebar push menu 147 | if (o.sidebarPushMenu) { 148 | $.AdminLTE.pushMenu.activate(o.sidebarToggleSelector); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/header-main/header-main.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Main Header 3 | * ---------------------- 4 | */ 5 | 6 | .main-header { 7 | position: relative; 8 | max-height: 100px; 9 | z-index: 1030; 10 | //Navbar 11 | > .navbar { 12 | .transition(margin-left @transition-speed @transition-fn); 13 | margin-bottom: 0; 14 | margin-left: @sidebar-width; 15 | border: none; 16 | min-height: @navbar-height; 17 | border-radius: 0; 18 | .layout-top-nav & { 19 | margin-left: 0!important; 20 | } 21 | } 22 | //Navbar search text input 23 | #navbar-search-input { 24 | background: rgba(255,255,255,.2); 25 | border-color: transparent; 26 | &:focus, 27 | &:active { 28 | border-color: rgba(0,0,0,.1)!important; 29 | background: rgba(255,255,255,.9); 30 | } 31 | &::-moz-placeholder { 32 | color: #ccc; 33 | opacity: 1; 34 | } 35 | &:-ms-input-placeholder { 36 | color: #ccc; 37 | } 38 | &::-webkit-input-placeholder { 39 | color: #ccc; 40 | } 41 | } 42 | //Navbar Right Menu 43 | .navbar-custom-menu, 44 | .navbar-right { 45 | float: right; 46 | @media (max-width: @screen-sm-max) { 47 | a { 48 | color: inherit; 49 | background: transparent; 50 | } 51 | } 52 | } 53 | .navbar-right { 54 | @media (max-width: @screen-header-collapse) { 55 | float: none; 56 | .navbar-collapse & { 57 | margin: 7.5px -15px; 58 | } 59 | > li { 60 | color: inherit; 61 | border: 0; 62 | } 63 | } 64 | } 65 | //Navbar toggle button 66 | .sidebar-toggle { 67 | float: left; 68 | background-color: transparent; 69 | background-image: none; 70 | padding: @navbar-padding-vertical @navbar-padding-horizontal; 71 | //Add the fontawesome bars icon 72 | font-family: fontAwesome; 73 | &:before { 74 | content: "\f0c9"; 75 | } 76 | &:hover { 77 | color: #fff; 78 | } 79 | &:focus, 80 | &:active { 81 | background: transparent; 82 | } 83 | } 84 | .sidebar-toggle .icon-bar { 85 | display: none; 86 | } 87 | //Navbar User Menu 88 | .navbar .nav > li.user > a { 89 | > .fa, 90 | > .glyphicon, 91 | > .ion { 92 | margin-right: 5px; 93 | } 94 | } 95 | 96 | //Labels in navbar 97 | .navbar .nav > li > a > .label { 98 | position: absolute; 99 | top: 9px; 100 | right: 7px; 101 | text-align: center; 102 | font-size: 9px; 103 | padding: 2px 3px; 104 | line-height: .9; 105 | } 106 | 107 | //Logo bar 108 | .logo { 109 | .transition(width @transition-speed @transition-fn); 110 | display: block; 111 | float: left; 112 | height: @navbar-height; 113 | font-size: 20px; 114 | line-height: 50px; 115 | text-align: center; 116 | width: @sidebar-width; 117 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 118 | padding: 0 15px; 119 | font-weight: 300; 120 | overflow: hidden; 121 | //Add support to sidebar mini by allowing the user to create 122 | //2 logo designs. mini and lg 123 | .logo-lg { 124 | //should be visibile when sidebar isn't collapsed 125 | display: block; 126 | } 127 | .logo-mini { 128 | display: none; 129 | } 130 | } 131 | //Navbar Brand. Alternative logo with layout-top-nav 132 | .navbar-brand { 133 | color: #fff; 134 | } 135 | } 136 | 137 | // Content Header 138 | .content-header { 139 | position: relative; 140 | padding: 15px 15px 0 15px; 141 | // Header Text 142 | > h1 { 143 | margin: 0; 144 | font-size: 24px; 145 | > small { 146 | font-size: 15px; 147 | display: inline-block; 148 | padding-left: 4px; 149 | font-weight: 300; 150 | } 151 | } 152 | 153 | > .breadcrumb { 154 | float: right; 155 | background: transparent; 156 | margin-top: 0px; 157 | margin-bottom: 0; 158 | font-size: 12px; 159 | padding: 7px 5px; 160 | position: absolute; 161 | top: 15px; 162 | right: 10px; 163 | .border-radius(2px); 164 | > li > a { 165 | color: #444; 166 | text-decoration: none; 167 | display: inline-block; 168 | > .fa, > .glyphicon, > .ion { 169 | margin-right: 5px; 170 | } 171 | } 172 | > li + li:before { 173 | content: '>\00a0'; 174 | } 175 | } 176 | 177 | @media (max-width: @screen-sm-max) { 178 | > .breadcrumb { 179 | position: relative; 180 | margin-top: 5px; 181 | top: 0; 182 | right: 0; 183 | float: none; 184 | background: @gray; 185 | padding-left: 10px; 186 | li:before { 187 | color: darken(@gray, 20%); 188 | } 189 | } 190 | } 191 | } 192 | .navbar-toggle { 193 | color: #fff; 194 | border: 0; 195 | margin: 0; 196 | padding: @navbar-padding-vertical @navbar-padding-horizontal; 197 | } 198 | //Control navbar scaffolding on x-small screens 199 | @media (max-width: @screen-sm-max) { 200 | .navbar-custom-menu .navbar-nav > li { 201 | float: left; 202 | } 203 | //Dont't let links get full width 204 | .navbar-custom-menu .navbar-nav { 205 | margin: 0; 206 | float: left; 207 | } 208 | 209 | .navbar-custom-menu .navbar-nav > li > a { 210 | padding-top: 15px; 211 | padding-bottom: 15px; 212 | line-height: 20px; 213 | } 214 | } 215 | 216 | // Collapse header 217 | @media (max-width: @screen-header-collapse) { 218 | .main-header { 219 | position: relative; 220 | .logo, 221 | .navbar { 222 | width: 100%; 223 | float: none; 224 | position: relative!important; 225 | } 226 | .navbar { 227 | margin: 0; 228 | } 229 | .navbar-custom-menu { 230 | float: right; 231 | } 232 | } 233 | .main-sidebar, 234 | .left-side { 235 | padding-top: 100px!important; 236 | } 237 | } 238 | 239 | .navbar-collapse.pull-left { 240 | @media(max-width: @screen-sm-max) { 241 | float: none!important; 242 | + .navbar-custom-menu { 243 | display: block; 244 | position: absolute; 245 | top: 0; 246 | right: 40px; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-right/side-bar-right.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Control sidebar. By default, this is the right sidebar. 3 | */ 4 | //The sidebar's background control class 5 | //This is a hack to make the background visible while scrolling 6 | .control-sidebar-bg { 7 | position: fixed; 8 | z-index: 1000; 9 | bottom: 0; 10 | } 11 | //Transitions 12 | .control-sidebar-bg, 13 | .control-sidebar { 14 | top: 0; 15 | right: -@control-sidebar-width; 16 | width: @control-sidebar-width; 17 | .transition(right @transition-speed ease-in-out); 18 | } 19 | //The sidebar 20 | .control-sidebar { 21 | position: absolute; 22 | padding-top: @navbar-height; 23 | z-index: 1010; 24 | //Fix position after header collapse 25 | @media (max-width: @screen-sm) { 26 | padding-top: @navbar-height + 50; 27 | } 28 | //Tab panes 29 | > .tab-content { 30 | padding: 10px 15px; 31 | } 32 | //Open state with slide over content effect 33 | &.control-sidebar-open { 34 | &, 35 | + .control-sidebar-bg { 36 | right: 0; 37 | } 38 | } 39 | } 40 | //Open without slide over content 41 | .control-sidebar-open { 42 | .control-sidebar-bg, 43 | .control-sidebar { 44 | right: 0; 45 | } 46 | @media(min-width: @screen-sm) { 47 | .content-wrapper, 48 | .right-side, 49 | .main-footer { 50 | margin-right: @control-sidebar-width; 51 | } 52 | } 53 | } 54 | //Control sidebar tabs 55 | .control-sidebar-tabs { 56 | > li { 57 | &:first-of-type > a { 58 | margin-left: 1px; 59 | &, 60 | &:hover { 61 | border-left-width: 0!important; 62 | } 63 | } 64 | > a { 65 | .border-radius(0)!important; 66 | //Hover and active states 67 | &, 68 | &:hover { 69 | border-top: none; 70 | border-right: none; 71 | border-left: 1px solid transparent!important; 72 | border-bottom: 1px solid transparent!important; 73 | } 74 | .icon { 75 | font-size: 16px; 76 | } 77 | } 78 | //Active state 79 | &.active { 80 | > a { 81 | &, 82 | &:hover, 83 | &:focus, 84 | &:active { 85 | border-top: none!important; 86 | border-right: none!important; 87 | border-bottom: none!important; 88 | } 89 | } 90 | } 91 | } 92 | //Remove responsiveness on small screens 93 | @media(max-width: @screen-sm) { 94 | display: table; 95 | >li { 96 | display: table-cell!important; 97 | } 98 | } 99 | } 100 | //Headings in the sidebar content 101 | .control-sidebar-heading { 102 | font-weight: 400; 103 | font-size: 16px; 104 | padding: 10px 0; 105 | margin-bottom: 10px; 106 | } 107 | //Subheadings 108 | .control-sidebar-subheading { 109 | display: block; 110 | font-weight: 400; 111 | font-size: 14px; 112 | } 113 | //Control Sidebar Menu 114 | .control-sidebar-menu { 115 | list-style: none; 116 | padding: 0; 117 | margin: 0 -15px; 118 | > li > a { 119 | .clearfix(); 120 | display: block; 121 | padding: 10px 15px; 122 | > .control-sidebar-subheading { 123 | margin-top: 0; 124 | } 125 | } 126 | .menu-icon { 127 | float: left; 128 | width: 35px; 129 | height: 35px; 130 | border-radius: 50%; 131 | text-align: center; 132 | line-height: 35px; 133 | } 134 | .menu-info { 135 | margin-left: 45px; 136 | margin-top: 3px; 137 | > .control-sidebar-subheading { 138 | margin: 0; 139 | } 140 | > p { 141 | margin: 0; 142 | font-size: 11px; 143 | } 144 | } 145 | .progress { 146 | margin: 0; 147 | } 148 | } 149 | //Dark skin 150 | .control-sidebar-dark { 151 | color: @sidebar-dark-color; 152 | // Background 153 | &, 154 | + .control-sidebar-bg { 155 | background: @sidebar-dark-bg; 156 | } 157 | // Sidebar tabs 158 | .control-sidebar-tabs { 159 | border-bottom: darken(@sidebar-dark-bg, 3%); 160 | > li { 161 | > a { 162 | background: darken(@sidebar-dark-bg, 5%); 163 | color: @sidebar-dark-color; 164 | //Hover and active states 165 | &, 166 | &:hover { 167 | border-left-color: darken(@sidebar-dark-bg, 7%)!important; 168 | border-bottom-color: darken(@sidebar-dark-bg, 7%)!important; 169 | } 170 | &:hover, 171 | &:focus, 172 | &:active { 173 | background: darken(@sidebar-dark-bg, 3%); 174 | } 175 | } 176 | //Active state 177 | &.active { 178 | > a { 179 | &, 180 | &:hover, 181 | &:focus, 182 | &:active { 183 | background: @sidebar-dark-bg; 184 | color: #fff; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | //Heading & subheading 191 | .control-sidebar-heading, 192 | .control-sidebar-subheading { 193 | color: #fff; 194 | } 195 | //Sidebar list 196 | .control-sidebar-menu { 197 | > li { 198 | > a { 199 | &:hover { 200 | background: @sidebar-dark-hover-bg; 201 | } 202 | .menu-info { 203 | > p { 204 | color: @sidebar-dark-color; 205 | } 206 | } 207 | } 208 | } 209 | } 210 | } 211 | //Light skin 212 | .control-sidebar-light { 213 | color: lighten(@sidebar-light-color, 10%); 214 | // Background 215 | &, 216 | + .control-sidebar-bg { 217 | background: @sidebar-light-bg; 218 | border-left: 1px solid @gray; 219 | } 220 | // Sidebar tabs 221 | .control-sidebar-tabs { 222 | border-bottom: @gray; 223 | > li { 224 | > a { 225 | background: darken(@sidebar-light-bg, 5%); 226 | color: @sidebar-light-color; 227 | //Hover and active states 228 | &, 229 | &:hover { 230 | border-left-color: @gray!important; 231 | border-bottom-color: @gray!important; 232 | } 233 | &:hover, 234 | &:focus, 235 | &:active { 236 | background: darken(@sidebar-light-bg, 3%); 237 | } 238 | } 239 | //Active state 240 | &.active { 241 | > a { 242 | &, 243 | &:hover, 244 | &:focus, 245 | &:active { 246 | background: @sidebar-light-bg; 247 | color: #111; 248 | } 249 | } 250 | } 251 | } 252 | } 253 | //Heading & subheading 254 | .control-sidebar-heading, 255 | .control-sidebar-subheading { 256 | color: #111; 257 | } 258 | //Sidebar list 259 | .control-sidebar-menu { 260 | margin-left: -14px; 261 | > li { 262 | > a { 263 | &:hover { 264 | background: @sidebar-light-hover-bg; 265 | } 266 | .menu-info { 267 | > p { 268 | color: lighten(@sidebar-light-color, 10%); 269 | } 270 | } 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-right/side-bar-right.html: -------------------------------------------------------------------------------- 1 | 167 | -------------------------------------------------------------------------------- /client/images/icons/Alien.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 61 | 79 | 96 | 114 | 131 | 149 | 166 | 184 | 185 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/side-bar-left/side-bar-left.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Component: Sidebar 3 | * ------------------ 4 | */ 5 | //Main Sidebar 6 | // ``` .left-side has been deprecated as of 2.0.0 in favor of .main-sidebar ``` 7 | 8 | .main-sidebar, 9 | .left-side { 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | padding-top: 50px; 14 | min-height: 100%; 15 | width: @sidebar-width; 16 | z-index: 810; 17 | //Using disposable variable to join statements with a comma 18 | @transition-rule: @transition-speed @transition-fn, 19 | width @transition-speed @transition-fn; 20 | .transition-transform(@transition-rule); 21 | @media (max-width: @screen-xs-max) { 22 | .translate(-@sidebar-width, 0); 23 | } 24 | .sidebar-collapse & { 25 | @media (min-width: @screen-sm) { 26 | .translate(-@sidebar-width, 0); 27 | } 28 | } 29 | .sidebar-open & { 30 | @media (max-width: @screen-xs-max) { 31 | .translate(0, 0); 32 | } 33 | } 34 | } 35 | 36 | .sidebar { 37 | padding-bottom: 10px; 38 | } 39 | // remove border from form 40 | .sidebar-form { 41 | input:focus { 42 | border-color: transparent!important; 43 | } 44 | } 45 | 46 | //Sidebar user panel 47 | .user-panel { 48 | position: relative; 49 | width: 100%; 50 | padding: 10px; 51 | overflow: hidden; 52 | .clearfix(); 53 | > .image > img { 54 | width: 100%; 55 | max-width: 45px; 56 | height: auto; 57 | } 58 | > .info { 59 | padding: 5px 5px 5px 15px; 60 | line-height: 1; 61 | position: absolute; 62 | left: 55px; 63 | > p { 64 | font-weight: 600; 65 | margin-bottom: 9px; 66 | } 67 | > a { 68 | text-decoration: none; 69 | padding-right: 5px; 70 | margin-top: 3px; 71 | font-size: 11px; 72 | > .fa, 73 | > .ion, 74 | > .glyphicon { 75 | margin-right: 3px; 76 | } 77 | } 78 | } 79 | } 80 | 81 | // Sidebar menu 82 | .sidebar-menu { 83 | list-style: none; 84 | margin: 0; 85 | padding: 0; 86 | //First Level 87 | > li { 88 | position: relative; 89 | margin: 0; 90 | padding: 0; 91 | > a { 92 | padding: 12px 5px 12px 15px; 93 | display: block; 94 | > .fa, 95 | > .glyphicon, 96 | > .ion { 97 | width: 20px; 98 | } 99 | } 100 | .label, 101 | .badge { 102 | margin-top: 3px; 103 | margin-right: 5px; 104 | } 105 | } 106 | li.header { 107 | padding: 10px 25px 10px 15px; 108 | font-size: 12px; 109 | } 110 | li > a > .fa-angle-left { 111 | width: auto; 112 | height: auto; 113 | padding: 0; 114 | margin-right: 10px; 115 | margin-top: 3px; 116 | } 117 | li.active { 118 | > a > .fa-angle-left { 119 | .rotate(-90deg); 120 | } 121 | > .treeview-menu { 122 | display: block; 123 | } 124 | } 125 | 126 | // Tree view menu 127 | .treeview-menu { 128 | display: none; 129 | list-style: none; 130 | padding:0; 131 | margin:0; 132 | padding-left: 5px; 133 | .treeview-menu { 134 | padding-left: 20px; 135 | } 136 | > li { 137 | margin: 0; 138 | > a { 139 | padding: 5px 5px 5px 15px; 140 | display: block; 141 | font-size: 14px; 142 | > .fa, 143 | > .glyphicon, 144 | > .ion { 145 | width: 20px; 146 | } 147 | > .fa-angle-left, 148 | > .fa-angle-down { 149 | width: auto; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | /* 157 | * Component: Sidebar Mini 158 | */ 159 | 160 | //Add sidebar-mini class to the body tag to activate this feature 161 | .sidebar-mini { 162 | //Sidebar mini should work only on devices larger than @screen-sm 163 | @media (min-width: @screen-sm) { 164 | //When the sidebar is collapsed... 165 | &.sidebar-collapse { 166 | 167 | //Apply the new margining to the main content and footer 168 | .content-wrapper, 169 | .right-side, 170 | .main-footer { 171 | margin-left: 50px!important; 172 | z-index: 840; 173 | } 174 | 175 | //Modify the sidebar to shrink instead of disappearing 176 | .main-sidebar { 177 | //Don't go away! Just shrink 178 | .translate(0, 0); 179 | width: 50px!important; 180 | z-index: 850; 181 | } 182 | 183 | .sidebar-menu { 184 | > li { 185 | position: relative; 186 | > a { 187 | margin-right: 0; 188 | } 189 | > a > span { 190 | border-top-right-radius: 4px; 191 | } 192 | 193 | &:not(.treeview) { 194 | > a > span { 195 | border-bottom-right-radius: 4px; 196 | } 197 | } 198 | 199 | > .treeview-menu { 200 | //Add some padding to the treeview menu 201 | padding-top: 5px; 202 | padding-bottom: 5px; 203 | border-bottom-right-radius: 4px; 204 | } 205 | 206 | //Show menu items on hover 207 | &:hover { 208 | > a { 209 | //overflow: visible; 210 | } 211 | > a > span:not(.pull-right), 212 | > .treeview-menu { 213 | display: block!important; 214 | position: absolute; 215 | width: @sidebar-width - 50; 216 | left: 50px; 217 | } 218 | 219 | //position the header & treeview menus 220 | > a > span { 221 | top: 0; 222 | margin-left: -3px; 223 | padding: 12px 5px 12px 20px; 224 | background-color: inherit; 225 | } 226 | > .treeview-menu { 227 | top: 44px; 228 | margin-left: 0; 229 | } 230 | } 231 | } 232 | } 233 | 234 | //Make the sidebar links, menus, labels, badges 235 | //and angle icons disappear 236 | .main-sidebar .user-panel > .info, 237 | .sidebar-form, 238 | .sidebar-menu > li > a > span, 239 | .sidebar-menu > li > .treeview-menu, 240 | .sidebar-menu >li > a > .pull-right, 241 | .sidebar-menu li.header { 242 | display: none!important; 243 | } 244 | 245 | .main-header { 246 | //Let's make the logo also shrink and the mini logo to appear 247 | .logo { 248 | width: 50px; 249 | > .logo-mini { 250 | display: block; 251 | margin-left: -15px; 252 | margin-right: -15px; 253 | font-size: 18px; 254 | } 255 | > .logo-lg { 256 | display: none; 257 | } 258 | } 259 | 260 | //Since the logo got smaller, we need to fix the navbar's position 261 | .navbar { 262 | margin-left: 50px; 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | //A fix for text overflow while transitioning from sidebar mini to full sidebar 270 | .sidebar-menu, 271 | .main-sidebar .user-panel, 272 | .sidebar-menu > li.header { 273 | white-space: nowrap!important; 274 | overflow: hidden; 275 | } 276 | .sidebar-menu:hover { 277 | overflow: visible; 278 | } 279 | .sidebar-form, 280 | .sidebar-menu > li.header { 281 | overflow: hidden; 282 | text-overflow: clip; 283 | } 284 | .sidebar-menu li > a { 285 | position: relative; 286 | > .pull-right { 287 | position: absolute; 288 | top: 50%; 289 | right: 10px; 290 | margin-top: -7px; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /client/src/components/layout-admin/style/miscellaneous.less: -------------------------------------------------------------------------------- 1 | /* 2 | * General: Miscellaneous 3 | * ---------------------- 4 | */ 5 | // 10px padding and margins 6 | .pad { 7 | padding: 10px; 8 | } 9 | .margin { 10 | margin: 10px; 11 | } 12 | .margin-bottom { 13 | margin-bottom: 20px; 14 | } 15 | // Display inline 16 | .inline { 17 | display: inline; 18 | width: auto; 19 | } 20 | 21 | // Description Blocks 22 | .description-block { 23 | display: block; 24 | margin: 10px 0; 25 | text-align: center; 26 | &.margin-bottom { 27 | margin-bottom: 25px; 28 | } 29 | > .description-header { 30 | margin: 0; 31 | padding: 0; 32 | font-weight: 600; 33 | font-size: 16px; 34 | } 35 | > .description-text { 36 | text-transform: uppercase; 37 | } 38 | } 39 | 40 | // Background colors 41 | .bg-red, 42 | .bg-yellow, 43 | .bg-aqua, 44 | .bg-blue, 45 | .bg-light-blue, 46 | .bg-green, 47 | .bg-navy, 48 | .bg-teal, 49 | .bg-olive, 50 | .bg-lime, 51 | .bg-orange , 52 | .bg-fuchsia, 53 | .bg-purple, 54 | .bg-maroon, 55 | .bg-black, 56 | .bg-red-active, 57 | .bg-yellow-active, 58 | .bg-aqua-active, 59 | .bg-blue-active, 60 | .bg-light-blue-active, 61 | .bg-green-active, 62 | .bg-navy-active, 63 | .bg-teal-active, 64 | .bg-olive-active, 65 | .bg-lime-active, 66 | .bg-orange-active, 67 | .bg-fuchsia-active, 68 | .bg-purple-active, 69 | .bg-maroon-active, 70 | .bg-black-active { 71 | color: #fff !important; 72 | } 73 | .bg-gray { 74 | color: #000; 75 | background-color: @gray!important; 76 | } 77 | .bg-black { 78 | background-color: @black!important; 79 | } 80 | .bg-red { 81 | background-color: @red !important; 82 | } 83 | .bg-yellow { 84 | background-color: @yellow !important; 85 | } 86 | .bg-aqua { 87 | background-color: @aqua !important; 88 | } 89 | .bg-blue { 90 | background-color: @blue !important; 91 | } 92 | .bg-light-blue { 93 | background-color: @light-blue !important; 94 | } 95 | .bg-green { 96 | background-color: @green !important; 97 | } 98 | .bg-navy { 99 | background-color: @navy !important; 100 | } 101 | .bg-teal { 102 | background-color: @teal !important; 103 | } 104 | .bg-olive { 105 | background-color: @olive !important; 106 | } 107 | .bg-lime { 108 | background-color: @lime !important; 109 | } 110 | .bg-orange { 111 | background-color: @orange !important; 112 | } 113 | .bg-fuchsia { 114 | background-color: @fuchsia !important; 115 | } 116 | .bg-purple { 117 | background-color: @purple !important; 118 | } 119 | .bg-maroon { 120 | background-color: @maroon !important; 121 | } 122 | 123 | //Set of Active Background Colors 124 | .bg-gray-active { 125 | color: #000; 126 | background-color: darken(@gray,10%)!important; 127 | } 128 | .bg-black-active { 129 | background-color: darken(@black, 10%)!important; 130 | } 131 | .bg-red-active { 132 | background-color: darken(@red , 6%)!important; 133 | } 134 | .bg-yellow-active { 135 | background-color: darken(@yellow , 6%)!important; 136 | } 137 | .bg-aqua-active { 138 | background-color: darken(@aqua , 6%)!important; 139 | } 140 | .bg-blue-active { 141 | background-color: darken(@blue , 10%)!important; 142 | } 143 | .bg-light-blue-active { 144 | background-color: darken(@light-blue , 6%)!important; 145 | } 146 | .bg-green-active { 147 | background-color: darken(@green , 5%)!important; 148 | } 149 | .bg-navy-active { 150 | background-color: darken(@navy , 2%)!important; 151 | } 152 | .bg-teal-active { 153 | background-color: darken(@teal , 5%)!important; 154 | } 155 | .bg-olive-active { 156 | background-color: darken(@olive , 5%)!important; 157 | } 158 | .bg-lime-active { 159 | background-color: darken(@lime , 5%)!important; 160 | } 161 | .bg-orange-active { 162 | background-color: darken(@orange , 5%)!important; 163 | } 164 | .bg-fuchsia-active { 165 | background-color: darken(@fuchsia , 5%)!important; 166 | } 167 | .bg-purple-active { 168 | background-color: darken(@purple , 5%)!important; 169 | } 170 | .bg-maroon-active { 171 | background-color: darken(@maroon , 3%)!important; 172 | } 173 | 174 | //Disabled! 175 | [class^="bg-"].disabled { 176 | .opacity(.65); 177 | } 178 | 179 | // Text colors 180 | .text-red { 181 | color: @red !important; 182 | } 183 | .text-yellow { 184 | color: @yellow !important; 185 | } 186 | .text-aqua { 187 | color: @aqua !important; 188 | } 189 | .text-blue { 190 | color: @blue !important; 191 | } 192 | .text-black { 193 | color: @black!important; 194 | } 195 | .text-light-blue { 196 | color: @light-blue !important; 197 | } 198 | .text-green { 199 | color: @green !important; 200 | } 201 | .text-gray { 202 | color: @gray !important; 203 | } 204 | .text-navy { 205 | color: @navy !important; 206 | } 207 | .text-teal { 208 | color: @teal !important; 209 | } 210 | .text-olive { 211 | color: @olive !important; 212 | } 213 | .text-lime { 214 | color: @lime !important; 215 | } 216 | .text-orange { 217 | color: @orange !important; 218 | } 219 | .text-fuchsia { 220 | color: @fuchsia !important; 221 | } 222 | .text-purple { 223 | color: @purple !important; 224 | } 225 | .text-maroon { 226 | color: @maroon !important; 227 | } 228 | 229 | // Hide elements by display none only 230 | .hide { 231 | display: none !important; 232 | } 233 | 234 | // Remove borders 235 | .no-border { 236 | border: 0px !important; 237 | } 238 | // Remove padding 239 | .no-padding { 240 | padding: 0px !important; 241 | } 242 | // Remove margins 243 | .no-margin { 244 | margin: 0px !important; 245 | } 246 | 247 | // Remove box shadow 248 | .no-shadow { 249 | box-shadow: none!important; 250 | } 251 | 252 | // Unstyled List 253 | .list-unstyled { 254 | list-style: none; 255 | margin: 0; 256 | padding: 0; 257 | } 258 | 259 | // Remove border radius 260 | .flat { 261 | .border-radius(0)!important; 262 | } 263 | 264 | .text-bold { 265 | &, &.table td, &.table th { 266 | font-weight: 700; 267 | } 268 | 269 | 270 | } 271 | 272 | // _fix for sparkline tooltip 273 | .jqstooltip{ 274 | padding: 5px!important; 275 | width:auto!important; 276 | height:auto!important; 277 | } 278 | 279 | 280 | // Gradient Background colors 281 | .bg-teal-gradient { 282 | .gradient(@teal; @teal; lighten(@teal, 16%))!important; 283 | color: #fff; 284 | } 285 | .bg-light-blue-gradient { 286 | .gradient(@light-blue; @light-blue; lighten(@light-blue, 12%))!important; 287 | color: #fff; 288 | } 289 | .bg-blue-gradient { 290 | .gradient(@blue; @blue; lighten(@blue, 7%))!important; 291 | color: #fff; 292 | } 293 | .bg-aqua-gradient { 294 | .gradient(@aqua; @aqua; lighten(@aqua, 7%))!important; 295 | color: #fff; 296 | } 297 | .bg-yellow-gradient { 298 | .gradient(@yellow; @yellow; lighten(@yellow, 16%))!important; 299 | color: #fff; 300 | } 301 | .bg-purple-gradient { 302 | .gradient(@purple; @purple; lighten(@purple, 16%))!important; 303 | color: #fff; 304 | } 305 | .bg-green-gradient { 306 | .gradient(@green; @green; lighten(@green, 7%))!important; 307 | color: #fff; 308 | } 309 | .bg-red-gradient { 310 | .gradient(@red; @red; lighten(@red, 10%))!important; 311 | color: #fff; 312 | } 313 | .bg-black-gradient { 314 | .gradient(@black; @black; lighten(@black, 10%))!important; 315 | color: #fff; 316 | } 317 | .bg-maroon-gradient { 318 | .gradient(@maroon; @maroon; lighten(@maroon, 10%))!important; 319 | color: #fff; 320 | } 321 | .connectedSortable { 322 | min-height: 100px; 323 | } 324 | .ui-helper-hidden-accessible { 325 | border: 0; 326 | clip: rect(0 0 0 0); 327 | height: 1px; 328 | margin: -1px; 329 | overflow: hidden; 330 | padding: 0; 331 | position: absolute; 332 | width: 1px; 333 | } 334 | .sort-highlight { 335 | background: #f4f4f4; 336 | border: 1px dashed #ddd; 337 | margin-bottom: 10px; 338 | } 339 | .full-opacity-hover { 340 | .opacity(.65); 341 | &:hover { 342 | .opacity(1); 343 | } 344 | } 345 | // Charts 346 | .chart { 347 | position: relative; 348 | overflow: hidden; 349 | width: 100%; 350 | svg, 351 | canvas { 352 | width: 100%!important; 353 | } 354 | } -------------------------------------------------------------------------------- /client/vendor/marker-clusterer/marker-clusterer.js: -------------------------------------------------------------------------------- 1 | function d(a){return function(b){this[a]=b}}function f(a){return function(){return this[a]}}var k; function l(a,b,c){this.extend(l,google.maps.OverlayView);this.b=a;this.a=[];this.f=[];this.da=[53,56,66,78,90];this.j=[];this.A=!1;c=c||{};this.g=c.gridSize||60;this.l=c.minimumClusterSize||2;this.K=c.maxZoom||null;this.j=c.styles||[];this.Y=c.imagePath||this.R;this.X=c.imageExtension||this.Q;this.P=!0;void 0!=c.zoomOnClick&&(this.P=c.zoomOnClick);this.r=!1;void 0!=c.averageCenter&&(this.r=c.averageCenter);m(this);this.setMap(a);this.L=this.b.getZoom();var e=this;google.maps.event.addListener(this.b, "zoom_changed",function(){var a=e.b.getZoom(),b=e.b.minZoom||0,c=Math.min(e.b.maxZoom||100,e.b.mapTypes[e.b.getMapTypeId()].maxZoom),a=Math.min(Math.max(a,b),c);e.L!=a&&(e.L=a,e.m())});google.maps.event.addListener(this.b,"idle",function(){e.i()});b&&(b.length||Object.keys(b).length)&&this.C(b,!1)}k=l.prototype;k.R="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";k.Q="png"; k.extend=function(a,b){return function(a){for(var b in a.prototype)this.prototype[b]=a.prototype[b];return this}.apply(a,[b])};k.onAdd=function(){this.A||(this.A=!0,p(this))};k.draw=function(){};function m(a){if(!a.j.length)for(var b=0,c;c=a.da[b];b++)a.j.push({url:a.Y+(b+1)+"."+a.X,height:c,width:c})}k.T=function(){for(var a=this.o(),b=new google.maps.LatLngBounds,c=0,e;e=a[c];c++)b.extend(e.getPosition());this.b.fitBounds(b)};k.w=f("j");k.o=f("a");k.W=function(){return this.a.length};k.ca=d("K"); k.J=f("K");k.G=function(a,b){for(var c=0,e=a.length,g=e;0!==g;)g=parseInt(g/10,10),c++;c=Math.min(c,b);return{text:e,index:c}};k.aa=d("G");k.H=f("G");k.C=function(a,b){if(a.length)for(var c=0,e;e=a[c];c++)s(this,e);else if(Object.keys(a).length)for(e in a)s(this,a[e]);b||this.i()};function s(a,b){b.s=!1;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.s=!1;a.M()});a.a.push(b)}k.q=function(a,b){s(this,a);b||this.i()}; function t(a,b){var c=-1;if(a.a.indexOf)c=a.a.indexOf(b);else for(var e=0,g;g=a.a[e];e++)if(g==b){c=e;break}if(-1==c)return!1;b.setMap(null);a.a.splice(c,1);return!0}k.Z=function(a,b){var c=t(this,a);return!b&&c?(this.m(),this.i(),!0):!1};k.$=function(a,b){for(var c=!1,e=0,g;g=a[e];e++)g=t(this,g),c=c||g;if(!b&&c)return this.m(),this.i(),!0};k.V=function(){return this.f.length};k.getMap=f("b");k.setMap=d("b");k.I=f("g");k.ba=d("g"); k.v=function(a){var b=this.getProjection(),c=new google.maps.LatLng(a.getNorthEast().lat(),a.getNorthEast().lng()),e=new google.maps.LatLng(a.getSouthWest().lat(),a.getSouthWest().lng()),c=b.fromLatLngToDivPixel(c);c.x+=this.g;c.y-=this.g;e=b.fromLatLngToDivPixel(e);e.x-=this.g;e.y+=this.g;c=b.fromDivPixelToLatLng(c);b=b.fromDivPixelToLatLng(e);a.extend(c);a.extend(b);return a};k.S=function(){this.m(!0);this.a=[]}; k.m=function(a){for(var b=0,c;c=this.f[b];b++)c.remove();for(b=0;c=this.a[b];b++)c.s=!1,a&&c.setMap(null);this.f=[]};k.M=function(){var a=this.f.slice();this.f.length=0;this.m();this.i();window.setTimeout(function(){for(var b=0,c;c=a[b];b++)c.remove()},0)};k.i=function(){p(this)}; function p(a){if(a.A)for(var b=new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast()),b=a.v(b),c=0,e;e=a.a[c];c++)if(!e.s&&b.contains(e.getPosition())){for(var g=a,u=4E4,q=null,x=0,n=void 0;n=g.f[x];x++){var h=n.getCenter();if(h){var r=e.getPosition();if(h&&r)var y=(r.lat()-h.lat())*Math.PI/180,z=(r.lng()-h.lng())*Math.PI/180,h=Math.sin(y/2)*Math.sin(y/2)+Math.cos(h.lat()*Math.PI/180)*Math.cos(r.lat()*Math.PI/180)*Math.sin(z/2)*Math.sin(z/2),h=12742*Math.atan2(Math.sqrt(h), Math.sqrt(1-h));else h=0;h=this.l&&a.setMap(null); a=this.b.getZoom();if((b=this.k.J())&&a>b)for(a=0;b=this.a[a];a++)b.setMap(this.b);else this.a.length