├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── README.md ├── app ├── .buildignore ├── .htaccess ├── 404.html ├── favicon.ico ├── images │ ├── responsive-design.png │ └── responsive-design.svg ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ ├── controllers │ │ ├── movie.js │ │ └── movies.js │ ├── directives │ │ ├── movieLink.js │ │ ├── scrollBar.js │ │ └── scrollDisplay.js │ └── services │ │ ├── bookmarks.js │ │ └── movies.js ├── styles │ ├── _grids.scss │ ├── main.scss │ └── mixin.scss └── views │ ├── includes │ ├── movie-navigation.html │ └── navigation.html │ ├── main │ ├── footer.html │ └── header.html │ └── pages │ ├── movie │ ├── movieInfo.html │ └── trailer.html │ └── movies │ ├── movie.preview.html │ ├── movie.summary.html │ └── movies.html ├── bower.json ├── config.example.json ├── config.json └── package.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | .idea 6 | bower_components 7 | config.json 8 | app/scripts/config.js 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "globals": { 21 | "angular": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install -g bower grunt-cli' 6 | - 'bower install' 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-12-24 using generator-angular 0.10.0 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Configurable paths for the application 19 | var appConfig = { 20 | app: require('./bower.json').appPath || 'app', 21 | dist: 'dist' 22 | }; 23 | 24 | // Define the configuration for all the tasks 25 | grunt.initConfig({ 26 | 27 | // Project settings 28 | yeoman: appConfig, 29 | 30 | // Watches files for changes and runs tasks based on the changed files 31 | watch: { 32 | bower: { 33 | files: ['bower.json'], 34 | tasks: ['wiredep'] 35 | }, 36 | js: { 37 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 38 | tasks: ['newer:jshint:all'], 39 | options: { 40 | livereload: '<%= connect.options.livereload %>' 41 | } 42 | }, 43 | jsTest: { 44 | files: ['test/spec/{,*/}*.js'], 45 | tasks: ['newer:jshint:test', 'karma'] 46 | }, 47 | compass: { 48 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 49 | tasks: ['compass:server', 'autoprefixer'] 50 | }, 51 | gruntfile: { 52 | files: ['Gruntfile.js'] 53 | }, 54 | livereload: { 55 | options: { 56 | livereload: '<%= connect.options.livereload %>' 57 | }, 58 | files: [ 59 | '<%= yeoman.app %>/{,*/}*.html', 60 | '.tmp/styles/{,*/}*.css', 61 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 62 | ] 63 | } 64 | }, 65 | 66 | // The actual grunt server settings 67 | connect: { 68 | options: { 69 | port: 9010, 70 | // Change this to '0.0.0.0' to access the server from outside. 71 | hostname: 'localhost', 72 | livereload: 35731 73 | }, 74 | livereload: { 75 | options: { 76 | open: true, 77 | middleware: function (connect) { 78 | return [ 79 | connect.static('.tmp'), 80 | connect().use( 81 | '/bower_components', 82 | connect.static('./bower_components') 83 | ), 84 | connect.static(appConfig.app) 85 | ]; 86 | } 87 | } 88 | }, 89 | test: { 90 | options: { 91 | port: 9001, 92 | middleware: function (connect) { 93 | return [ 94 | connect.static('.tmp'), 95 | connect.static('test'), 96 | connect().use( 97 | '/bower_components', 98 | connect.static('./bower_components') 99 | ), 100 | connect.static(appConfig.app) 101 | ]; 102 | } 103 | } 104 | }, 105 | dist: { 106 | options: { 107 | open: true, 108 | base: '<%= yeoman.dist %>' 109 | } 110 | } 111 | }, 112 | 113 | // Make sure code styles are up to par and there are no obvious mistakes 114 | jshint: { 115 | options: { 116 | jshintrc: '.jshintrc', 117 | reporter: require('jshint-stylish') 118 | }, 119 | all: { 120 | src: [ 121 | 'Gruntfile.js', 122 | '<%= yeoman.app %>/scripts/{,*/}*.js' 123 | ] 124 | }, 125 | test: { 126 | options: { 127 | jshintrc: 'test/.jshintrc' 128 | }, 129 | src: ['test/spec/{,*/}*.js'] 130 | } 131 | }, 132 | 133 | // Empties folders to start fresh 134 | clean: { 135 | dist: { 136 | files: [{ 137 | dot: true, 138 | src: [ 139 | '.tmp', 140 | '<%= yeoman.dist %>/{,*/}*', 141 | '!<%= yeoman.dist %>/.git{,*/}*' 142 | ] 143 | }] 144 | }, 145 | server: '.tmp' 146 | }, 147 | 148 | // Add vendor prefixed styles 149 | autoprefixer: { 150 | options: { 151 | browsers: ['last 1 version'] 152 | }, 153 | dist: { 154 | files: [{ 155 | expand: true, 156 | cwd: '.tmp/styles/', 157 | src: '{,*/}*.css', 158 | dest: '.tmp/styles/' 159 | }] 160 | } 161 | }, 162 | 163 | // Automatically inject Bower components into the app 164 | wiredep: { 165 | app: { 166 | src: ['<%= yeoman.app %>/index.html'], 167 | ignorePath: /\.\.\// 168 | }, 169 | sass: { 170 | src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 171 | ignorePath: /(\.\.\/){1,2}bower_components\// 172 | } 173 | }, 174 | 175 | // Compiles Sass to CSS and generates necessary files if requested 176 | compass: { 177 | options: { 178 | sassDir: '<%= yeoman.app %>/styles', 179 | cssDir: '.tmp/styles', 180 | generatedImagesDir: '.tmp/images/generated', 181 | imagesDir: '<%= yeoman.app %>/images', 182 | javascriptsDir: '<%= yeoman.app %>/scripts', 183 | fontsDir: '<%= yeoman.app %>/styles/fonts', 184 | importPath: './bower_components', 185 | httpImagesPath: '/images', 186 | httpGeneratedImagesPath: '/images/generated', 187 | httpFontsPath: '/styles/fonts', 188 | relativeAssets: false, 189 | assetCacheBuster: false, 190 | raw: 'Sass::Script::Number.precision = 10\n' 191 | }, 192 | dist: { 193 | options: { 194 | generatedImagesDir: '<%= yeoman.dist %>/images/generated' 195 | } 196 | }, 197 | server: { 198 | options: { 199 | debugInfo: true 200 | } 201 | } 202 | }, 203 | 204 | // Renames files for browser caching purposes 205 | filerev: { 206 | dist: { 207 | src: [ 208 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 209 | '<%= yeoman.dist %>/styles/{,*/}*.css', 210 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 211 | '<%= yeoman.dist %>/styles/fonts/*' 212 | ] 213 | } 214 | }, 215 | 216 | // Environment configuration. 217 | ngconstant: { 218 | options: { 219 | name: 'config' 220 | }, 221 | server: { 222 | constants: { 223 | Config: grunt.file.readJSON('config.json').development 224 | }, 225 | values: { 226 | debug: true 227 | }, 228 | options: { 229 | dest: '<%= yeoman.app %>/scripts/config.js' 230 | } 231 | }, 232 | build: { 233 | constants: { 234 | Config: grunt.file.readJSON('config.json').production 235 | }, 236 | options: { 237 | dest: '<%= yeoman.dist %>/scripts/config.js' 238 | } 239 | } 240 | }, 241 | 242 | // Build - Deploy site to repository. 243 | buildcontrol: { 244 | dist: { 245 | options: { 246 | remote: 'git@github.com:YaronMiro/AngularJS-UI-Router-Demo.git', 247 | branch: 'gh-pages', 248 | commit: true, 249 | push: true 250 | } 251 | } 252 | }, 253 | 254 | // Reads HTML for usemin blocks to enable smart builds that automatically 255 | // concat, minify and revision files. Creates configurations in memory so 256 | // additional tasks can operate on them 257 | useminPrepare: { 258 | html: '<%= yeoman.app %>/index.html', 259 | options: { 260 | dest: '<%= yeoman.dist %>', 261 | flow: { 262 | html: { 263 | steps: { 264 | js: ['concat', 'uglifyjs'], 265 | css: ['cssmin'] 266 | }, 267 | post: {} 268 | } 269 | } 270 | } 271 | }, 272 | 273 | // Performs rewrites based on filerev and the useminPrepare configuration 274 | usemin: { 275 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 276 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 277 | options: { 278 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] 279 | } 280 | }, 281 | 282 | // The following *-min tasks will produce minified files in the dist folder 283 | // By default, your `index.html`'s will take care of 284 | // minification. These next options are pre-configured if you do not wish 285 | // to use the Usemin blocks. 286 | // cssmin: { 287 | // dist: { 288 | // files: { 289 | // '<%= yeoman.dist %>/styles/main.css': [ 290 | // '.tmp/styles/{,*/}*.css' 291 | // ] 292 | // } 293 | // } 294 | // }, 295 | // uglify: { 296 | // dist: { 297 | // files: { 298 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 299 | // '<%= yeoman.dist %>/scripts/scripts.js' 300 | // ] 301 | // } 302 | // } 303 | // }, 304 | // concat: { 305 | // dist: {} 306 | // }, 307 | 308 | imagemin: { 309 | dist: { 310 | files: [{ 311 | expand: true, 312 | cwd: '<%= yeoman.app %>/images', 313 | src: '{,*/}*.{png,jpg,jpeg,gif}', 314 | dest: '<%= yeoman.dist %>/images' 315 | }] 316 | } 317 | }, 318 | 319 | svgmin: { 320 | dist: { 321 | files: [{ 322 | expand: true, 323 | cwd: '<%= yeoman.app %>/images', 324 | src: '{,*/}*.svg', 325 | dest: '<%= yeoman.dist %>/images' 326 | }] 327 | } 328 | }, 329 | 330 | htmlmin: { 331 | dist: { 332 | options: { 333 | collapseWhitespace: true, 334 | conservativeCollapse: true, 335 | collapseBooleanAttributes: true, 336 | removeCommentsFromCDATA: true, 337 | removeOptionalTags: true 338 | }, 339 | files: [{ 340 | expand: true, 341 | cwd: '<%= yeoman.dist %>', 342 | src: ['*.html', 'views/**/*.html'], 343 | dest: '<%= yeoman.dist %>' 344 | }] 345 | } 346 | }, 347 | 348 | // ng-annotate tries to make the code safe for minification automatically 349 | // by using the Angular long form for dependency injection. 350 | ngAnnotate: { 351 | dist: { 352 | files: [{ 353 | expand: true, 354 | cwd: '.tmp/concat/scripts', 355 | src: ['*.js', '!oldieshim.js'], 356 | dest: '.tmp/concat/scripts' 357 | }] 358 | } 359 | }, 360 | 361 | // Replace Google CDN references 362 | cdnify: { 363 | dist: { 364 | html: ['<%= yeoman.dist %>/*.html'] 365 | } 366 | }, 367 | 368 | // Copies remaining files to places other tasks can use 369 | copy: { 370 | dist: { 371 | files: [{ 372 | expand: true, 373 | dot: true, 374 | cwd: '<%= yeoman.app %>', 375 | dest: '<%= yeoman.dist %>', 376 | src: [ 377 | '*.{ico,png,txt}', 378 | '.htaccess', 379 | '*.html', 380 | 'views/**/*.html', 381 | 'images/{,*/}*.{webp}', 382 | 'fonts/{,*/}*.*' 383 | ] 384 | }, { 385 | expand: true, 386 | cwd: '.tmp/images', 387 | dest: '<%= yeoman.dist %>/images', 388 | src: ['generated/*'] 389 | }, { 390 | expand: true, 391 | cwd: '.', 392 | src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*', 393 | dest: '<%= yeoman.dist %>' 394 | }] 395 | }, 396 | styles: { 397 | expand: true, 398 | cwd: '<%= yeoman.app %>/styles', 399 | dest: '.tmp/styles/', 400 | src: '{,*/}*.css' 401 | } 402 | }, 403 | 404 | // Run some tasks in parallel to speed up the build process 405 | concurrent: { 406 | server: [ 407 | 'compass:server' 408 | ], 409 | test: [ 410 | 'compass' 411 | ], 412 | dist: [ 413 | 'compass:dist', 414 | 'imagemin', 415 | 'svgmin' 416 | ] 417 | }, 418 | 419 | // Test settings 420 | karma: { 421 | unit: { 422 | configFile: 'test/karma.conf.js', 423 | singleRun: true 424 | } 425 | } 426 | }); 427 | 428 | 429 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 430 | if (target === 'dist') { 431 | return grunt.task.run(['build', 'connect:dist:keepalive']); 432 | } 433 | 434 | grunt.task.run([ 435 | 'clean:server', 436 | 'ngconstant:server', 437 | 'wiredep', 438 | 'concurrent:server', 439 | 'autoprefixer', 440 | 'connect:livereload', 441 | 'watch' 442 | ]); 443 | }); 444 | 445 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 446 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 447 | grunt.task.run(['serve:' + target]); 448 | }); 449 | 450 | grunt.registerTask('test', [ 451 | 'clean:server', 452 | 'concurrent:test', 453 | 'autoprefixer', 454 | 'connect:test', 455 | ]); 456 | 457 | grunt.registerTask('build', [ 458 | 'clean:dist', 459 | 'ngconstant:build', 460 | 'wiredep', 461 | 'useminPrepare', 462 | 'concurrent:dist', 463 | 'autoprefixer', 464 | 'concat', 465 | 'ngAnnotate', 466 | 'copy:dist', 467 | 'cdnify', 468 | 'cssmin', 469 | 'uglify', 470 | 'filerev', 471 | 'usemin', 472 | 'htmlmin' 473 | ]); 474 | 475 | grunt.registerTask('deploy', [ 476 | 'test', 477 | 'build', 478 | 'buildcontrol' 479 | ]); 480 | 481 | grunt.registerTask('default', [ 482 | 'newer:jshint', 483 | 'test', 484 | 'build' 485 | ]); 486 | }; 487 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJs-UI-Router---Example 2 | ============================= 3 | 4 | Demo project for the AngularJs UI-Router. 5 | 6 | ##Build locally. 7 | 8 | ```bash 9 | bundle install 10 | npm install 11 | bower install 12 | grunt build 13 | ``` 14 | 15 | ##Open in browser. 16 | 17 | ```bash 18 | grunt serve 19 | ``` 20 | -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YaronMiro/AngularJS-UI-Router-Demo/c01c7248eff9427eaad5d9f7ec07e4b0bfbf9338/app/favicon.ico -------------------------------------------------------------------------------- /app/images/responsive-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YaronMiro/AngularJS-UI-Router-Demo/c01c7248eff9427eaad5d9f7ec07e4b0bfbf9338/app/images/responsive-design.png -------------------------------------------------------------------------------- /app/images/responsive-design.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | simpleicon.com 7 | Collection Of Flat Icon, Symbols And Glyph Icons 8 | 9 | 12 | 18 | 25 | 27 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Itunes top 60 movies 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name myAppApp 6 | * @description 7 | * # myAppApp 8 | * 9 | * Main module of the application. 10 | */ 11 | angular 12 | .module('myApp', [ 13 | 'ui.router', 14 | 'ngAnimate', 15 | 'config', 16 | 'angular-loading-bar', 17 | 'LocalStorageModule', 18 | 'duScroll' 19 | ]) 20 | .config(['$stateProvider', '$urlRouterProvider', 'localStorageServiceProvider', function($stateProvider, $urlRouterProvider, localStorageServiceProvider){ 21 | 22 | // Setting a local storage "prefix" to avoid overwriting another data. 23 | // on the local storage. 24 | localStorageServiceProvider.setPrefix('myApp'); 25 | 26 | /** 27 | * Redirect a user to homepage. 28 | * 29 | * @param $state 30 | * The ui-router state. 31 | * @param $selectedMovie 32 | * The target movie. 33 | */ 34 | var redirect = function($state, selectedMovie) { 35 | if (!angular.isDefined(selectedMovie)) { 36 | // if the movie doesn't exist then redirect to the "parent" state. 37 | // in our case it's the main "movies" state. 38 | $state.go('main.movies'); 39 | } 40 | }; 41 | 42 | /** 43 | * Redirect a user to homepage. 44 | * 45 | * @param moviesData 46 | * Array of movie {*}. 47 | * @param $stateParams 48 | * The state url params {*}. 49 | * @param $filter 50 | * The $filter service {*}. 51 | * 52 | * Return the target movie {*}. 53 | */ 54 | var gettingSelectedMovie = function(moviesData, $stateParams, $filter){ 55 | var selectedMovie = $filter('filter')(moviesData, {urlAlias: $stateParams.name}); 56 | return selectedMovie[0]; 57 | }; 58 | 59 | // Default url route. 60 | $urlRouterProvider.otherwise('/movies'); 61 | 62 | $stateProvider 63 | // The main view. 64 | .state('main',{ 65 | url: '/', 66 | abstract: true, 67 | views: { 68 | // Absolutely targets the 'header' view in this state. 69 | //
within index.html. 70 | 'header': { 71 | templateUrl: 'views/main/header.html' 72 | }, 73 | // Absolutely targets the 'footer' view in this state. 74 | //
within index.html. 75 | 'footer': { 76 | templateUrl: 'views/main/footer.html' 77 | } 78 | } 79 | }) 80 | 81 | // Movies state. 82 | .state('main.movies',{ 83 | url: 'movies', 84 | views: { 85 | // Relatively targets the 'content' view in this state parent state, 86 | // 'main'.
within index.html. 87 | 'content@': { 88 | templateUrl: 'views/pages/movies/movies.html', 89 | controller: 'moviesController', 90 | controllerAs: 'movies' 91 | }, 92 | // Absolutely targets the 'preview' view in this state. 93 | //
within movies.html. 94 | 'preview@main.movies': { 95 | templateUrl: 'views/pages/movies/movie.preview.html' 96 | }, 97 | // Absolutely targets the 'summary' view in this state. 98 | //
within movies.html. 99 | 'summary@main.movies': { 100 | templateUrl: 'views/pages/movies/movie.summary.html' 101 | } 102 | }, 103 | resolve: { 104 | // Example showing injection of service into resolve function. 105 | // Service then returns a promise. 106 | moviesData: function(Movies){ 107 | return Movies.gettingMovies(); 108 | } 109 | } 110 | }) 111 | 112 | // Bookmarks Movies state. 113 | .state('main.bookmarks',{ 114 | url: 'bookmarks', 115 | views: { 116 | // Relatively targets the 'content' view in this state parent state, 117 | // 'main'.
within index.html. 118 | 'content@': { 119 | templateUrl: 'views/pages/movies/movies.html', 120 | controller: 'moviesController', 121 | controllerAs: 'movies' 122 | }, 123 | // Absolutely targets the 'preview' view in this state. 124 | //
within movies.html. 125 | 'preview@main.bookmarks': { 126 | templateUrl: 'views/pages/movies/movie.preview.html' 127 | }, 128 | // Absolutely targets the 'summary' view in this state. 129 | //
within movies.html. 130 | 'summary@main.bookmarks': { 131 | templateUrl: 'views/pages/movies/movie.summary.html' 132 | } 133 | }, 134 | resolve: { 135 | moviesData: function(Bookmarks){ 136 | return Bookmarks.getMovies(); 137 | } 138 | } 139 | }) 140 | 141 | // Movies state. 142 | .state('main.movie',{ 143 | url: 'movie/{name}?originBookmark', 144 | abstract: true, 145 | resolve: { 146 | moviesData: function(Movies, Bookmarks, $stateParams){ 147 | // Set the data type according to the movie origin. 148 | return (parseInt($stateParams.originBookmark)) ? Bookmarks.getMovies(): Movies.gettingMovies(); 149 | } 150 | }, 151 | data: { 152 | trailer: { 153 | basePath: 'http://www.youtube.com/embed/?listType=search&list=', 154 | params: { 155 | controls: 2, 156 | modestbranding: 1, 157 | rel: 0, 158 | showinfo: 0, 159 | autoplay: 1, 160 | hd: 1 161 | } 162 | } 163 | } 164 | }) 165 | 166 | // Single movie state. 167 | .state('main.movie.movieInfo',{ 168 | // The "^" character excludes the parent prefix url 169 | // ("movie/{name}?originBookmark") format from this child state url, it 170 | // will become only as "movie/info/{name}?originBookmark". 171 | url: '^/movie/info/{name}?originBookmark', 172 | views: { 173 | 'content@': { 174 | templateUrl: 'views/pages/movie/movieInfo.html', 175 | controller: 'movieController', 176 | controllerAs: 'movie' 177 | } 178 | }, 179 | resolve: { 180 | // Example showing injection of a "parent" resolve object 181 | // into it's child resolve function. 182 | selectedMovie: gettingSelectedMovie 183 | }, 184 | onEnter: redirect, 185 | data: { 186 | breadcrumbs: 'info' 187 | } 188 | }) 189 | 190 | // Single movie state. 191 | .state('main.movie.trailer',{ 192 | // The "^" character excludes the parent prefix url 193 | // ("movie/{name}?originBookmark") format from this child state url, it 194 | // will become only as "movie/trailer/{name}?originBookmark". 195 | url: '^/movie/trailer/{name}?originBookmark', 196 | views: { 197 | 'content@': { 198 | templateUrl: 'views/pages/movie/trailer.html', 199 | controller: 'movieController', 200 | controllerAs: 'movie' 201 | } 202 | }, 203 | resolve: { 204 | // Example showing injection of a "parent" resolve object 205 | // into it's child resolve function. 206 | selectedMovie: gettingSelectedMovie 207 | }, 208 | onEnter: redirect, 209 | data: { 210 | breadcrumbs: 'trailer' 211 | } 212 | }) 213 | }]) 214 | .run([ '$rootScope', '$state', '$stateParams', 'localStorageService', 'Bookmarks', function ($rootScope, $state, $stateParams, localStorageService, Bookmarks) { 215 | // It's very handy to add references to $state and $stateParams to the 216 | // $rootScope so that you can access them from any scope within your 217 | // applications. 218 | $rootScope.$state = $state; 219 | $rootScope.$stateParams = $stateParams; 220 | $rootScope.parseInt = parseInt; 221 | 222 | // Helper to debug on template file. 223 | $rootScope.console = function(data) { 224 | return console.log(data); 225 | }; 226 | 227 | // Access local storage service from any scope. 228 | $rootScope.localStorageService = localStorageService; 229 | }]) 230 | -------------------------------------------------------------------------------- /app/scripts/controllers/movie.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name myAppApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the myApp 9 | */ 10 | angular.module('myApp') 11 | .controller('movieController', ['$state', 'Movies', 'selectedMovie', 'moviesData','Bookmarks', function ($state, Movies, selectedMovie, moviesData, Bookmarks) { 12 | 13 | var self = this; 14 | 15 | // Array of movies. 16 | self.movies = moviesData; 17 | 18 | // Bookmarks service object. 19 | self.bookmarksService = Bookmarks; 20 | 21 | // Selected movie. 22 | self.selectedMovie = selectedMovie; 23 | 24 | // Movie trailer. 25 | self.movieTrailerUrl = Movies.gettingMovieTrailerUrl(self.selectedMovie.trackName, $state.current.data.trailer); 26 | 27 | }]); 28 | -------------------------------------------------------------------------------- /app/scripts/controllers/movies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name myAppApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the myApp 9 | */ 10 | angular.module('myApp') 11 | .controller('moviesController', ['moviesData','Bookmarks', function (moviesData, Bookmarks) { 12 | 13 | var self = this; 14 | 15 | // Movies data. 16 | self.data = moviesData; 17 | 18 | // Bookmarks service object. 19 | self.bookmarksService = Bookmarks; 20 | 21 | }]); 22 | -------------------------------------------------------------------------------- /app/scripts/directives/movieLink.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp') 4 | .directive('movieLink', ['$state', '$filter', function ($state, $filter) { 5 | return { 6 | restrict: 'A', 7 | scope: { 8 | movieLink: '@', 9 | selectedMovieIndex: '=', 10 | movies: '=' 11 | }, 12 | link: function (scope, element) { 13 | 14 | // Defaults. 15 | var index = scope.selectedMovieIndex; 16 | var viewName = $state.current.name; 17 | var length = scope.movies.length; 18 | var eClass = scope.movieLink; 19 | 20 | // Apply element text. 21 | element.text(scope.movieLink); 22 | element.addClass(eClass); 23 | 24 | // In case we are at the "first" or "last" movie disable the irrelevant 25 | // element from the display and add class to design the disabled style. 26 | // and exit without further process. 27 | if (scope.movieLink == 'previous' && index == 1 || scope.movieLink == 'next' && index == length) { 28 | element.attr('disabled', 'true'); 29 | element.addClass('disabled'); 30 | return; 31 | } 32 | 33 | var changeSelectedMovie = function() { 34 | // Proceed to next movie if it exists. 35 | if (scope.movieLink == 'next' && index != length) { 36 | index = index + 1; 37 | } 38 | 39 | // Proceed to previous movie if it exists. 40 | if (scope.movieLink == 'previous' && index > 1) { 41 | index = index - 1; 42 | } 43 | 44 | var selectedMovie = $filter('filter')(scope.movies, {index: index}); 45 | var selectedMovie = selectedMovie[0]; 46 | $state.go(viewName, {'name': selectedMovie.urlAlias}); 47 | } 48 | 49 | // Update element. 50 | element.bind('click', changeSelectedMovie); 51 | 52 | } 53 | }; 54 | }]); 55 | -------------------------------------------------------------------------------- /app/scripts/directives/scrollBar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('myApp') 3 | .directive('scrollBar', function () { 4 | 5 | return { 6 | restrict: 'EA', 7 | 8 | link: function(scope, element) { 9 | 10 | // Adding scrollbar js library. 11 | element.mCustomScrollbar({ 12 | setHeight: 75, 13 | theme: 'light-thick' 14 | }); 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/directives/scrollDisplay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('myApp') 3 | .directive('scrollDisplay', ['$window', function ($window) { 4 | return { 5 | restrict: 'EA', 6 | 7 | link: function (scope) { 8 | 9 | angular.element($window).bind("scroll", function () { 10 | if (this.pageYOffset >= 300) { 11 | scope.scrollDisplay = true; 12 | } else { 13 | scope.scrollDisplay = false; 14 | } 15 | scope.$apply(); 16 | }); 17 | } 18 | }; 19 | }]); 20 | -------------------------------------------------------------------------------- /app/scripts/services/bookmarks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp') 4 | .factory('Bookmarks', ['localStorageService', '$filter', '$q', function (localStorageService, $filter, $q) { 5 | 6 | // Private data array of movies. 7 | var data =[]; 8 | data = localStorageService.get('bookmarks'); 9 | data = data != null ? data : new Array(); 10 | 11 | return { 12 | 13 | /** 14 | * Save a movie to the local storage "movies" array. 15 | * On success return true else return false. 16 | * 17 | * @param movie 18 | * The movie object {*}. 19 | * 20 | * @returns bool 21 | */ 22 | addToBookmarks: function(movie) { 23 | 24 | var deferred = $q.defer(); 25 | 26 | // Create a deep 27 | var movieCopy = {}; 28 | angular.copy(movie, movieCopy); 29 | 30 | // Get array of movies. 31 | var movies = data; 32 | 33 | // Adding a flag to the movie object to reference it's relationship 34 | // to the bookmark type movie. 35 | movieCopy.originBookmark = 1; 36 | movieCopy.isBookmarked = true; 37 | 38 | // Set a new index for the incoming movie. 39 | movieCopy.index = movies.length ? (movies.length + 1) : 1; 40 | 41 | // Add movie to the data object. 42 | movies.push(movieCopy); 43 | 44 | // Update the local storage value. 45 | var save = localStorageService.set('bookmarks', movies); 46 | 47 | // In case of success. 48 | if (save) { 49 | // On success mark the movie as bookmarked. 50 | movie.isBookmarked = true; 51 | 52 | deferred.resolve({"saved": save, "error": false}); 53 | } 54 | // In case of error. 55 | else { 56 | deferred.reject({"saved": save, "error": true}); 57 | } 58 | 59 | // Return promise object. 60 | return deferred.promise; 61 | }, 62 | 63 | /** 64 | * Remove a movie to the local storage "movies" array. 65 | * 66 | * @param movie 67 | * The target movie to exclude object {*}. 68 | * 69 | */ 70 | removeFromBookmarks: function(movie) { 71 | 72 | var deferred = $q.defer(); 73 | 74 | // Get array of movies. 75 | var movies = data; 76 | 77 | // Find the target movie from with in the movies array. 78 | var targetMovie = $filter('filter')(movies, {id: movie.id}); 79 | 80 | // Remove movie from array of movies by it's (index value - 1). 81 | movies.splice((targetMovie[0].index -1), 1); 82 | 83 | // Re-order movies each movie index value. 84 | angular.forEach(movies, function(movie, index) { 85 | movie.index = (index + 1); 86 | }); 87 | 88 | // Update the local storage value. 89 | var deleted = localStorageService.set('bookmarks', movies); 90 | 91 | // In case of success. 92 | if (deleted) { 93 | 94 | // On success un-mark the movie as bookmarked. 95 | movie.isBookmarked = false; 96 | 97 | deferred.resolve({"deleted": deleted, "error": false}); 98 | } 99 | // In case of error. 100 | else { 101 | deferred.reject({"deleted": deleted, "error": true}); 102 | } 103 | 104 | // Return promise object. 105 | return deferred.promise; 106 | }, 107 | 108 | /** 109 | * Get all of the movies from the local storage "movies" array. 110 | * On success return the value from the local storage. 111 | * 112 | * @returns bool 113 | */ 114 | getMovies: function() { 115 | var deferred = $q.defer(); 116 | deferred.resolve(data); 117 | // Return promise object. 118 | return deferred.promise; 119 | }, 120 | 121 | /** 122 | * Check if a movie is bookmarked. 123 | * if bookmarked then it returns true, else returns false. 124 | * 125 | * @param movieId 126 | * The movie unique id. 127 | * 128 | * @returns bool 129 | */ 130 | isMovieBookmarked: function(movieId) { 131 | var movies = data; 132 | // Find the target movie from with in the movies array. 133 | var targetMovie = $filter('filter')(movies, {id: movieId}); 134 | return targetMovie.length ? true : false; 135 | } 136 | } 137 | 138 | }]); 139 | -------------------------------------------------------------------------------- /app/scripts/services/movies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp') 4 | .factory('Movies', ['$http', '$q', '$sce', 'Bookmarks', function ($http, $q, $sce, Bookmarks) { 5 | 6 | /** 7 | * Return the promise {*} with the list of top movies Ids amount by moviesCount. 8 | * 9 | * @param moviesCount 10 | * Number of movies to return. 11 | * 12 | * @returns {*} 13 | */ 14 | function requestTopMoviesIds(moviesCount) { 15 | var deferred = $q.defer(); 16 | $http({ 17 | method: 'GET', 18 | url: 'https://itunes.apple.com/us/rss/topmovies/limit=' + moviesCount + '/json' 19 | }) 20 | .success(function(data) { 21 | var moviesIds = []; 22 | // Get the top 30 movies IDs. 23 | angular.forEach(data.feed.entry, function(movie) { 24 | moviesIds.push(movie.id.attributes['im:id']); 25 | }); 26 | deferred.resolve(moviesIds); 27 | }) 28 | 29 | // Return promise object. 30 | return deferred.promise; 31 | } 32 | 33 | /** 34 | * Return the promise {*} with the list of top movies data. 35 | * 36 | * @param ids 37 | * An array of movies IDs. 38 | * 39 | * @returns {*} 40 | */ 41 | function requestMoviesById(ids) { 42 | 43 | // Exit early if it's not an array. 44 | if (!angular.isArray(ids)) { 45 | return; 46 | } 47 | 48 | var deferred = $q.defer(); 49 | $http.jsonp('https://itunes.apple.com/lookup', {params: { id: ids.join(), callback: 'JSON_CALLBACK'}}) 50 | .success(function(movies) { 51 | 52 | angular.forEach(movies.results, function(movie, index) { 53 | 54 | // Adding unique id for each movie. 55 | movie.id = ids[index]; 56 | 57 | // Flag to identify if the movie is bookmarked. 58 | movie.isBookmarked = Bookmarks.isMovieBookmarked(movie.id); 59 | 60 | // Adding a flag to the movie object to reference it's relationship 61 | // to the bookmark type movie. 62 | movie.originBookmark = 0; 63 | 64 | // Adding index for each movie. 65 | movie.index = index + 1; 66 | 67 | // Ad an extra movie image size 600px width. 68 | movie.artworkUrl600 = movie.artworkUrl100.replace('100x100', "600x600"); 69 | 70 | // pretty url - we will replace it with a cleaner structure. 71 | // (e.g) movie-info/movie%20name/1 => movie-info/movie-name/1. 72 | movie.urlAlias = movie.trackName.replace(/ /g, '-').toLowerCase(); 73 | }); 74 | 75 | deferred.resolve(movies.results); 76 | }) 77 | // Return promise object. 78 | return deferred.promise; 79 | } 80 | 81 | // Public API here 82 | return { 83 | /** 84 | * Return a promise object of the list of Movies. 85 | * 86 | * @returns {*} 87 | */ 88 | gettingMovies: function(moviesCount) { 89 | var deferred = $q.defer(); 90 | 91 | moviesCount = angular.isDefined(moviesCount) ? moviesCount : 60; 92 | 93 | // Get the top movies ids. 94 | requestTopMoviesIds(moviesCount).then(function(moviesIds) { 95 | requestMoviesById(moviesIds).then(function(movies) { 96 | deferred.resolve(movies); 97 | }) 98 | }); 99 | 100 | // Return promise object. 101 | return deferred.promise; 102 | }, 103 | 104 | /** 105 | * Generates "youtube" movie trailer url. 106 | * 107 | * @param movieName 108 | * The search query. 109 | * @param movieData 110 | * The movie data (config data). 111 | * 112 | * @returns {string} 113 | */ 114 | 115 | gettingMovieTrailerUrl: function(movieName, movieData) { 116 | 117 | // Prepare the query string with the movie params. 118 | var params = []; 119 | angular.forEach(movieData.params, function(value, param){ 120 | this.push(param + '=' + value); 121 | }, params); 122 | 123 | // Joining the params. 124 | params = '&' + params.join('&'); 125 | // Making sure the url is valid and trusted. 126 | return $sce.trustAsResourceUrl(encodeURI(movieData.basePath + movieName + ' ' + 'trailer' + params)) 127 | } 128 | }; 129 | }]); 130 | -------------------------------------------------------------------------------- /app/styles/_grids.scss: -------------------------------------------------------------------------------- 1 | // Minimum screen size for Medium screen / tablet. 2 | $screen-sm-min: ($screen-is-max + 1); 3 | 4 | // Container width for the custom grid. 5 | // 6 | // Set the container width, and override it for fixed navbars in media queries. 7 | .container { 8 | @media (min-width: $screen-is-min) and (max-width: $screen-is-max) { 9 | width: $ContainerPhone; 10 | } 11 | } 12 | 13 | // Function that creates the custom grid 14 | // Intermediate small grid 15 | @media (min-width: $screen-is-min) and (max-width: $screen-is-max) { 16 | @include make-grid(is); 17 | } 18 | 19 | // Add visibility "visible-is" class for the custom grid 20 | @media (min-width: $screen-is-min) and (max-width: $screen-is-max) { 21 | @include responsive-visibility('.visible-is'); 22 | } 23 | 24 | // Add hidden "hidden-is" class for the custom grid 25 | @media (min-width: $screen-is-min) and (max-width: $screen-is-max) { 26 | @include responsive-invisibility('.hidden-is'); 27 | } 28 | 29 | // Define the "visible-is" class as hidden in all other grid widths (Except in the custom grid). 30 | @media (min-width: $screen-sm-min) { 31 | @include responsive-invisibility('.visible-is'); 32 | } 33 | 34 | @media (max-width: $screen-xs-max) { 35 | @include responsive-invisibility('.visible-is'); 36 | } 37 | -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "mixin"; 2 | 3 | body { 4 | font-size: 13px; 5 | font-family: 'Play', sans-serif; 6 | padding-top: 20px; 7 | padding-bottom: 20px; 8 | background: black; 9 | } 10 | 11 | .border { 12 | @include border(1px); 13 | 14 | &.thick { 15 | height: 2px; 16 | } 17 | 18 | &.m-top { 19 | margin-top: 10px; 20 | } 21 | 22 | &.m-bottom { 23 | margin-bottom: 10px; 24 | } 25 | } 26 | 27 | #main { 28 | 29 | @include phone-only { 30 | padding: 0px 30px; 31 | } 32 | 33 | #movies { 34 | .movies-notification { 35 | margin: 8px 0px; 36 | padding: 10px 0px; 37 | color: $lightGray; 38 | @include borders(1px); 39 | } 40 | } 41 | 42 | #main-header, 43 | #main-footer { 44 | 45 | .top, 46 | .bottom { 47 | margin: 0px; 48 | height: 15px; 49 | text-align: center; 50 | background: $darkGray; 51 | } 52 | } 53 | 54 | #main-header { 55 | color: $gray; 56 | text-transform: uppercase; 57 | 58 | .top { 59 | margin-top: 15px; 60 | @include border-top-left-radius(6px); 61 | @include border-top-right-radius(6px); 62 | } 63 | 64 | .navbar { 65 | margin-bottom: 0px; 66 | 67 | .navbar-toggle { 68 | 69 | border: 1px solid $blue; 70 | 71 | .menu-icon { 72 | font-size: 18px; 73 | color: $blue; 74 | } 75 | } 76 | } 77 | 78 | h1 { 79 | font-size: 24px; 80 | color: #ffffff; 81 | margin-top: 6px; 82 | margin-bottom: 0; 83 | line-height: 40px; 84 | 85 | @include tablet-to-desktop-only { 86 | font-size: 41px; 87 | } 88 | } 89 | 90 | .nav-pills { 91 | 92 | li { 93 | cursor: pointer; 94 | margin: 0px; 95 | padding: 0px; 96 | 97 | @include phone-only { 98 | position: relative; 99 | padding: 15px 0px; 100 | float: none; 101 | 102 | &:after, 103 | &:last-child:before { 104 | content: ""; 105 | width: 100%; 106 | position: absolute; 107 | left: 0px; 108 | @include border(1px); 109 | } 110 | 111 | &:last-child:before { 112 | bottom: 0px; 113 | } 114 | 115 | &:after { 116 | top: 0px; 117 | } 118 | } 119 | 120 | &.active { 121 | a { 122 | border-color: $blue; 123 | color: $blue; 124 | } 125 | } 126 | 127 | &:not(:last-child) { 128 | @include exclude-phone-only { 129 | margin-right: 10px; 130 | } 131 | } 132 | 133 | a { 134 | color: white; 135 | background: transparent; 136 | border: 1px solid white; 137 | font-size: 22px; 138 | 139 | @include phone-only { 140 | padding: 0px; 141 | text-align: center; 142 | border: none; 143 | } 144 | 145 | &:hover { 146 | color: $blue; 147 | border-color: $blue 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | #main-footer { 155 | 156 | .bottom { 157 | @include border-bottom-left-radius(6px); 158 | @include border-bottom-right-radius(6px); 159 | } 160 | 161 | .responsive-icon { 162 | 163 | .icon { 164 | color: $lightGray; 165 | 166 | &:before { 167 | content: ""; 168 | background-size: 40px auto; 169 | background-image: url("../images/responsive-design.png"); 170 | background-repeat: no-repeat; 171 | display: inline-block; 172 | width: 40px; 173 | height: 24px; 174 | margin-right: 5px; 175 | vertical-align: middle; 176 | } 177 | } 178 | } 179 | 180 | .footer-links{ 181 | list-style: none; 182 | margin: 15px 0px 0px 0px; 183 | padding: 0px; 184 | 185 | @include phone-to-mobile-only { 186 | text-align: center; 187 | } 188 | 189 | li { 190 | display: inline; 191 | padding-right: 3px; 192 | 193 | &:hover { 194 | a span { 195 | color: #337ab7; 196 | } 197 | } 198 | 199 | &:not(:first-child):not(:last-child):before { 200 | content: "|"; 201 | padding-right: 3px; 202 | color: $lightGray; 203 | } 204 | 205 | a { 206 | color: white; 207 | text-decoration: none; 208 | 209 | &.gizra { 210 | font-size: 17px; 211 | font-weight: lighter; 212 | font-family: 'Abril Fatface'; 213 | 214 | &:hover { 215 | color: #e27058; 216 | } 217 | } 218 | 219 | span { 220 | font-size: 18px; 221 | color: $gray; 222 | } 223 | } 224 | } 225 | } 226 | } 227 | 228 | .movies-wrapper { 229 | 230 | @include large-desktop-only { 231 | height: 570px; 232 | } 233 | 234 | @include medium-desktop-only { 235 | height: 470px; 236 | } 237 | 238 | @include tablet-only { 239 | height: 360px; 240 | } 241 | 242 | @include mobile-only { 243 | height: 345px; 244 | } 245 | 246 | .preview { 247 | cursor: pointer; 248 | @include transition(opacity 0.5s ease-in-out); 249 | 250 | img { 251 | width: 100%; 252 | 253 | @include exclude-phone-only { 254 | @include img-responsive; 255 | } 256 | } 257 | 258 | &:hover { 259 | @include opacity(0.6); 260 | } 261 | } 262 | 263 | .summary { 264 | color: #ffffff; 265 | text-align: center; 266 | 267 | h3 { 268 | padding: 7px 0px; 269 | text-transform: uppercase; 270 | color: white; 271 | margin: 20px 0px 14px 0px; 272 | font-size: 16px; 273 | font-weight: bold; 274 | @include borders(2px); 275 | 276 | @include medium-desktop-only { 277 | margin: 20px 0px 9px 0px; 278 | } 279 | } 280 | 281 | header { 282 | 283 | .rating { 284 | margin-bottom: 10px; 285 | padding: 5px; 286 | display: inline-block; 287 | @include border-radius(5px); 288 | background: $blue; 289 | 290 | i { 291 | color: $lightGray; 292 | } 293 | } 294 | 295 | .data { 296 | position: relative; 297 | color: rgba(255, 255, 255, 0.77); 298 | padding: 14px 0px; 299 | 300 | @include medium-desktop-only { 301 | padding: 7px 0px; 302 | } 303 | 304 | &:after { 305 | content: ""; 306 | width: 100%; 307 | position: absolute; 308 | left: 0px; 309 | @include border(1px); 310 | } 311 | 312 | &:after { 313 | top: 0px; 314 | } 315 | 316 | span { 317 | color: $lightGray; 318 | } 319 | } 320 | } 321 | } 322 | } 323 | 324 | .full { 325 | padding-left: 30px; 326 | padding-right: 30px; 327 | 328 | h3 { 329 | margin: 0px 0px 13px 0px; 330 | font-weight: bold; 331 | color: white; 332 | padding: 12px 0px; 333 | @include borders(2px); 334 | } 335 | 336 | 337 | .info-container, .image-container { 338 | padding-bottom: 30px; 339 | } 340 | 341 | .image-container { 342 | 343 | .image-wrapper { 344 | padding: 1px; 345 | margin-left: 30px; 346 | @include background-image(linear-gradient(black 5%, $blue 95%)); 347 | 348 | @include phone-to-mobile-only { 349 | margin-left: 0px; 350 | } 351 | 352 | .background { 353 | background: black; 354 | padding: 0px 19px 19px 19px; 355 | 356 | .popularity-ranking, .bookmarked-icon { 357 | bottom: 50px; 358 | } 359 | 360 | .popularity-ranking { 361 | right: 20px; 362 | } 363 | 364 | .bookmarked-icon { 365 | left: 50px; 366 | 367 | 368 | @include phone-only { 369 | left: 20px; 370 | } 371 | } 372 | 373 | img { 374 | width: 100%; 375 | } 376 | } 377 | } 378 | } 379 | 380 | .info-container { 381 | 382 | .info-wrapper { 383 | margin-right: 30px; 384 | 385 | @include phone-to-mobile-only { 386 | margin-right: 0px; 387 | } 388 | 389 | @include phone-only { 390 | padding-top: 0px; 391 | } 392 | 393 | .description { 394 | margin-top: 15px; 395 | padding-bottom: 20px; 396 | color: $transparentGray; 397 | text-align: justify; 398 | } 399 | 400 | .gradient-wrapper { 401 | padding: 1px; 402 | @include background-image(linear-gradient($blue 10%, black 100%)); 403 | 404 | .details { 405 | background: black; 406 | margin: 0; 407 | padding: 7px 0px 0px 20px; 408 | list-style: none; 409 | 410 | li { 411 | position: relative; 412 | padding: 7px 0px; 413 | color: $transparentGray; 414 | 415 | &> * { 416 | font-size: 13px; 417 | } 418 | 419 | &:not(:first-child):after { 420 | content: ""; 421 | width: 100%; 422 | position: absolute; 423 | left: 0px; 424 | top: 0px; 425 | @include border(1px); 426 | } 427 | 428 | &.add, &.remove { 429 | margin-left: -20px; 430 | cursor: pointer; 431 | 432 | label { 433 | display: block; 434 | cursor: inherit; 435 | font-size: 12px; 436 | text-transform: uppercase; 437 | } 438 | 439 | .icon { 440 | font-size: 30px; 441 | margin-bottom: 4px; 442 | } 443 | } 444 | 445 | &.add:hover .icon { 446 | color: $green; 447 | } 448 | 449 | &.remove:hover .icon { 450 | color: $red; 451 | } 452 | 453 | &:last-child { 454 | margin-top: 20px; 455 | padding: 10px 0px; 456 | 457 | &:before { 458 | content: ""; 459 | width: 100%; 460 | position: absolute; 461 | left: 0px; 462 | bottom: 0px; 463 | @include border(1px); 464 | } 465 | } 466 | 467 | label { 468 | cursor: inherit; 469 | font-weight: normal; 470 | font-size: 16px; 471 | margin: 0px; 472 | } 473 | 474 | i { 475 | &:after { 476 | content: " | "; 477 | } 478 | } 479 | 480 | a { 481 | text-decoration: none; 482 | color: $transparentGray; 483 | margin-top: 5px; 484 | display: block; 485 | } 486 | } 487 | } 488 | 489 | .trailer { 490 | width: 100%; 491 | margin: 0 auto; 492 | background: black; 493 | 494 | &:hover { 495 | .link, .link label { 496 | color: $blue; 497 | } 498 | } 499 | 500 | .link { 501 | cursor: pointer; 502 | margin: 0px; 503 | padding-top: 25px; 504 | color: $transparentGray; 505 | 506 | label { 507 | color: $transparentGray; 508 | cursor: inherit; 509 | margin: 0px; 510 | font-size: 12px; 511 | text-transform: uppercase; 512 | display: inline-block; 513 | } 514 | } 515 | 516 | .wrapper { 517 | margin-top: 7px; 518 | @include borders(1px); 519 | 520 | i { 521 | font-size: 30px; 522 | padding: 6px 0px; 523 | } 524 | } 525 | } 526 | } 527 | } 528 | } 529 | } 530 | } 531 | 532 | .trailer { 533 | 534 | h3 { 535 | padding: 12px 0px; 536 | margin: 13px 0px 30px 0px; 537 | color: white; 538 | @include borders(2px); 539 | } 540 | 541 | .movie-trailer { 542 | iframe { 543 | width: 100%; 544 | height: 500px; 545 | } 546 | } 547 | } 548 | 549 | .movie-links { 550 | border: 1px solid rgba(255, 255, 255, 0.32); 551 | border-radius: 6px; 552 | margin-top: 10px; 553 | margin-bottom: 30px; 554 | 555 | button.icon { 556 | padding: 8px; 557 | background: transparent; 558 | border: none; 559 | outline: none; 560 | display: inline-block; 561 | font: normal normal normal 14px/1 FontAwesome; 562 | font-size: inherit; 563 | text-rendering: auto; 564 | -webkit-font-smoothing: antialiased; 565 | -moz-osx-font-smoothing: grayscale; 566 | transform: translate(0, 0); 567 | 568 | &:hover:not(:disabled) { 569 | color: $blue; 570 | } 571 | 572 | &.previous:before, 573 | &.next:after { 574 | color: $blue; 575 | } 576 | 577 | &.previous.disabled:before, 578 | &.next.disabled:after { 579 | color: rgba(255, 255, 255, 0.32); 580 | } 581 | 582 | &.previous:before { 583 | margin-right: 4px; 584 | content: "\f137"; 585 | } 586 | 587 | &.next:after { 588 | content: "\f138"; 589 | margin-left: 4px; 590 | } 591 | } 592 | } 593 | 594 | .bookmarks-notification { 595 | .text { 596 | padding: 13px 0px; 597 | display: inline-block; 598 | color: $lightGray; 599 | margin: 10px 0px; 600 | @include borders(1px); 601 | } 602 | 603 | .btn-close { 604 | position: absolute; 605 | right: -20px; 606 | top: 8px; 607 | cursor: pointer; 608 | color: rgba(255, 255, 255, 0.21); 609 | } 610 | } 611 | 612 | .popularity-ranking, .bookmarked-icon { 613 | position: absolute; 614 | padding: 10px; 615 | bottom: 0px; 616 | font-weight: bold; 617 | background: rgba(0, 0, 0, 0.25); 618 | } 619 | 620 | .popularity-ranking { 621 | right: 0px; 622 | color: white; 623 | @include border-top-left-radius(6px); 624 | } 625 | 626 | .bookmarked-icon { 627 | left: 0px; 628 | color: white; 629 | @include border-top-right-radius(6px); 630 | } 631 | 632 | .color-effect { 633 | @include color-change-effect(); 634 | } 635 | 636 | .links { 637 | margin: 0px; 638 | padding: 0px; 639 | list-style: none; 640 | margin-top: 7px; 641 | 642 | @include medium-to-large-desktop-only { 643 | margin-top: 0px; 644 | } 645 | 646 | @include phone-only { 647 | margin-bottom: 22px; 648 | } 649 | 650 | @include tablet-only { 651 | margin-top: 0px; 652 | } 653 | 654 | li { 655 | position: relative; 656 | padding: 9px 0px; 657 | cursor: pointer; 658 | font-size: 27px; 659 | 660 | @include medium-desktop-only { 661 | padding: 6px 0px; 662 | font-size: 24px; 663 | } 664 | 665 | @include tablet-only { 666 | padding: 4px 0px; 667 | font-size: 25px; 668 | } 669 | 670 | @include mobile-only { 671 | padding: 4px 0px; 672 | font-size: 21px; 673 | } 674 | 675 | &:after, 676 | &:last-child:before { 677 | content: ""; 678 | width: 100%; 679 | position: absolute; 680 | left: 0px; 681 | @include border(1px); 682 | } 683 | 684 | &:last-child:before { 685 | bottom: 0px; 686 | } 687 | 688 | &:after { 689 | top: 0px; 690 | } 691 | 692 | .title { 693 | margin: 0px; 694 | font-size: 12px; 695 | text-transform: uppercase; 696 | color: $lightGray; 697 | } 698 | 699 | &:hover { 700 | color: $blue; 701 | } 702 | 703 | &.add:hover { 704 | color: $green; 705 | } 706 | 707 | &.remove:hover { 708 | color: $red; 709 | } 710 | } 711 | } 712 | 713 | // Scrollbar theme 714 | .mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail, .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail { 715 | background-color: rgba(2, 194, 255, 0.24); 716 | } 717 | 718 | .fadein, 719 | .fadeout { 720 | 721 | &.ng-enter, &.ng-hide-remove { 722 | opacity: 0; 723 | -webkit-transition: opacity 1.0s ease; 724 | -moz-transition: opacity 1.0s ease; 725 | -o-transition: opacity 1.0s ease; 726 | transition: opacity 1.0s ease; 727 | } 728 | 729 | &.ng-enter-active, &.ng-hide-remove-active { 730 | opacity: 1; 731 | } 732 | 733 | &.ng-leave-active, &.ng-hide-add-active { 734 | opacity: 0; 735 | } 736 | } 737 | 738 | .repeat-animate.ng-move, 739 | .repeat-animate.ng-enter, 740 | .repeat-animate.ng-leave { 741 | -webkit-transition: opacity linear 0.5s ease; 742 | -moz-transition: opacity linear 0.5s ease; 743 | -o-transition: opacity linear 0.5s ease; 744 | transition: opacity linear 0.5s ease; 745 | } 746 | 747 | .repeat-animate.ng-leave.ng-leave-active, 748 | .repeat-animate.ng-move, 749 | .repeat-animate.ng-enter { 750 | opacity:0; 751 | } 752 | 753 | .repeat-animate.ng-leave, 754 | .repeat-animate.ng-move.ng-move-active, 755 | .repeat-animate.ng-enter.ng-enter-active { 756 | opacity:1; 757 | } 758 | -------------------------------------------------------------------------------- /app/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @import "compass/css3/border-radius"; 2 | @import "compass/css3/transition"; 3 | @import "compass/css3/opacity"; 4 | @import "compass/css3/transform"; 5 | @import "compass/css3/box-shadow"; 6 | @import "compass/css3/images"; 7 | 8 | // Override default bootstrap variables. 9 | $grid-gutter-width: 0px; 10 | 11 | // Bootstrap icons. 12 | $icon-font-path: "../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/"; 13 | 14 | // This container is a custom one, Bootstrap out of the box has 3 containers. 15 | $ContainerPhone: 460px; 16 | 17 | // Intermediate small screen / phone. (Custom breakpoint). 18 | $screen-is: 480px; 19 | 20 | // Minimum screen size for the custom grid. 21 | $screen-is-min: ($screen-is); 22 | 23 | // Maximum screen size for the custom grid. 24 | $screen-is-max: 767px; 25 | 26 | // Maximum screen size for Extra small screen / phone (We have to call it before bootstrap). 27 | $screen-xs-max: ($screen-is-min - 1); 28 | 29 | // Navbar collapse 30 | //** Point at which the navbar becomes uncollapsed. 31 | $grid-float-breakpoint: $screen-is-min; 32 | 33 | // bower:scss 34 | @import "bootstrap-sass-official/assets/stylesheets/_bootstrap.scss"; 35 | // endbower 36 | 37 | 38 | // Including the custom grid css. 39 | @import "_grids.scss"; 40 | 41 | // Colors. 42 | $gray: #777777; 43 | $darkGray: #1e1e1e; 44 | $lightGray: rgba(255, 255, 255, 0.5); 45 | $transparentGray: rgba(255, 255, 255, 0.61); 46 | $blue: rgb(51, 122, 183); 47 | $green: #72aa25; 48 | $red: rgb(179, 41, 41); 49 | 50 | // Default transition 51 | $effect: ease-in; 52 | 53 | 54 | 55 | // Color change transition 56 | @mixin color-change-effect { 57 | @include transition-property(color, border); 58 | @include transition-duration(0.3s); 59 | @include transition-timing-function($effect); 60 | } 61 | 62 | // Color change transition 63 | @mixin border($height) { 64 | background: $blue; 65 | height: $height; 66 | background: -moz-linear-gradient(left, rgba(0,0,0,0) 0%, rgba(41, 137, 216, 0.51) 50%, rgba(0,0,0,0) 100%); /* FF3.6+ */ 67 | background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(0,0,0,0)), color-stop(50%,rgba(41, 137, 216, 0.51)), color-stop(100%,rgba(0,0,0,0))); /* Chrome,Safari4+ */ 68 | background: -webkit-linear-gradient(left, rgba(0,0,0,0) 0%,rgba(41, 137, 216, 0.51) 50%,rgba(0,0,0,0) 100%); /* Chrome10+,Safari5.1+ */ 69 | background: -o-linear-gradient(left, rgba(0,0,0,0) 0%,rgba(41, 137, 216, 0.51) 50%,rgba(0,0,0,0) 100%); /* Opera 11.10+ */ 70 | background: -ms-linear-gradient(left, rgba(0,0,0,0) 0%,rgba(41, 137, 216, 0.51) 50%,rgba(0,0,0,0) 100%); /* IE10+ */ 71 | background: linear-gradient(to right, rgba(0,0,0,0) 0%,rgba(41, 137, 216, 0.51) 50%,rgba(0,0,0,0) 100%); /* W3C */ 72 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#00000000',GradientType=1 ); /* IE6-9 */ 73 | } 74 | 75 | @mixin borders($height) { 76 | position: relative; 77 | 78 | &:after, 79 | &:before { 80 | content: ""; 81 | width: 100%; 82 | position: absolute; 83 | left: 0px; 84 | @include border($height); 85 | } 86 | 87 | &::before { 88 | bottom: 0px; 89 | } 90 | 91 | &:after { 92 | top: 0px; 93 | } 94 | } 95 | 96 | /* Large Devices, Wide Screens */ 97 | @mixin large-desktop-only { 98 | @media (min-width: $screen-lg-min) { 99 | @content; 100 | } 101 | } 102 | 103 | /* Medium Devices, Desktops */ 104 | @mixin medium-desktop-only { 105 | @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { 106 | @content; 107 | } 108 | } 109 | 110 | // Medium to large desktop. 111 | @mixin medium-to-large-desktop-only { 112 | @media (min-width: $screen-md-min) { 113 | @content; 114 | } 115 | } 116 | 117 | /* Small Devices, Tablets */ 118 | @mixin tablet-only { 119 | @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { 120 | @content; 121 | } 122 | } 123 | 124 | /* Extra Small Devices, Phones */ 125 | @mixin tablet-to-desktop-only { 126 | @media (min-width: $screen-sm-min) { 127 | @content; 128 | } 129 | } 130 | 131 | /* Extra Small Devices, Phones */ 132 | @mixin mobile-only { 133 | @media (min-width: $screen-is-min) and (max-width: $screen-is-max) { 134 | @content; 135 | } 136 | } 137 | 138 | // Not for mobile width only. 139 | @mixin exclude-mobile-only { 140 | @media (min-width: $screen-is-min) { 141 | @content; 142 | } 143 | } 144 | 145 | /* Custom, iPhone Retina */ 146 | @mixin phone-only { 147 | @media (max-width: $screen-xs-max) { 148 | @content; 149 | } 150 | } 151 | 152 | // Not for phone width only. 153 | @mixin exclude-phone-only { 154 | @media (min-width: $screen-xs-max) { 155 | @content; 156 | } 157 | } 158 | 159 | // Not for phone to mobile width only. 160 | @mixin phone-to-mobile-only { 161 | @media (max-width: $screen-is-max) { 162 | @content; 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /app/views/includes/movie-navigation.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 | -------------------------------------------------------------------------------- /app/views/includes/navigation.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /app/views/main/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 26 |
27 | -------------------------------------------------------------------------------- /app/views/main/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Itunes top 60 movies 5 | My bookmarks 6 | - {{ $state.current.data.breadcrumbs }} 7 |

8 |
9 |
10 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | In order to use this feature you must enable your browser - local storage capability. 28 | 29 |
30 |
31 | 32 | Bookmarked movies are stored on your browser local storage. 33 | 34 |
35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /app/views/pages/movie/movieInfo.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |
7 | 8 |
9 |

{{ movie.selectedMovie.trackName }}

10 | {{ movie.selectedMovie.trackName }}-poster image 11 |
{{ movie.selectedMovie.index }}
12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 | 23 |
{{ movie.selectedMovie.longDescription }}
24 |
25 | 26 |
    27 | 28 |
  • 29 | 30 |
    {{ movie.selectedMovie.releaseDate | date:'longDate' }}
    31 |
  • 32 | 33 |
  • 34 | 35 |
    {{ movie.selectedMovie.primaryGenreName }}
    36 |
  • 37 | 38 |
  • 39 | 40 |
    {{ movie.selectedMovie.artistName }}
    41 |
  • 42 | 43 |
  • 44 | 45 | HD: {{ movie.selectedMovie.collectionHdPrice }}$ 46 | | 47 | Rental HD: {{ movie.selectedMovie.trackHdRentalPrice }}$ 48 |
  • 49 | 50 |
  • 51 | 52 |
    {{ movie.selectedMovie.contentAdvisoryRating }}
    53 |
  • 54 |
  • 55 | 56 |
  • 57 |
  • 58 |
    59 | 60 | 61 |
    62 |
  • 63 |
  • 64 |
    65 | 66 | 67 |
    68 |
  • 69 |
70 | 71 |
72 | 76 |
77 |
78 |
79 |
80 |
81 | -------------------------------------------------------------------------------- /app/views/pages/movie/trailer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |

{{ movie.selectedMovie.trackName }} - Trailer

6 | 7 |
8 |