├── .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 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
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 |
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 |
2 |
3 | -
4 |
5 |
6 |
7 | -
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/views/main/footer.html:
--------------------------------------------------------------------------------
1 |
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 |
19 |
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 |
{{ movie.selectedMovie.trackName }}
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 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/views/pages/movie/trailer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ movie.selectedMovie.trackName }} - Trailer
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/views/pages/movies/movie.preview.html:
--------------------------------------------------------------------------------
1 |
2 |
![{{ movie.trackName }}-poster image]()
3 |
{{ movie.index }}
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/views/pages/movies/movie.summary.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ movie.trackName }}
3 |
20 |
21 | -
22 |
23 |
Poster
24 |
25 | -
26 |
27 |
Trailer
28 |
29 | -
30 |
31 |
Read more
32 |
33 | -
34 |
35 |
Add to bookmarks
36 |
37 | -
38 |
39 |
Remove from bookmarks
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/views/pages/movies/movies.html:
--------------------------------------------------------------------------------
1 |
2 | Currently there are no bookmarked movies
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "angular": "^1.3.0",
6 | "json3": "^3.3.0",
7 | "es5-shim": "^4.0.0",
8 | "bootstrap-sass-official": "^3.2.0",
9 | "angular-animate": "^1.3.0",
10 | "angular-route": "^1.3.0",
11 | "angular-ui-router": "~0.2.13",
12 | "angular-loading-bar": "~0.6.0",
13 | "malihu-custom-scrollbar-plugin": "~3.0.8",
14 | "angular-local-storage": "~0.1.5",
15 | "angular-scroll": "~0.6.5"
16 | },
17 | "devDependencies": {
18 | "angular-mocks": "~1.3.0",
19 | "angular-scenario": "~1.3.0"
20 | },
21 | "appPath": "app",
22 | "resolutions": {
23 | "angular": "^1.3.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "debugUiRouter": true
4 | },
5 | "production": {
6 | "debugUiRouter": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "debugUiRouter": true
4 | },
5 | "production": {
6 | "debugUiRouter": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myapp",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "devDependencies": {
6 | "grunt": "^0.4.1",
7 | "grunt-autoprefixer": "^0.7.3",
8 | "grunt-build-control": "^0.2.2",
9 | "grunt-concurrent": "^0.5.0",
10 | "grunt-contrib-clean": "^0.5.0",
11 | "grunt-contrib-compass": "^0.7.2",
12 | "grunt-contrib-concat": "^0.4.0",
13 | "grunt-contrib-connect": "^0.7.1",
14 | "grunt-contrib-copy": "^0.5.0",
15 | "grunt-contrib-cssmin": "^0.9.0",
16 | "grunt-contrib-htmlmin": "^0.3.0",
17 | "grunt-contrib-imagemin": "^0.8.1",
18 | "grunt-contrib-jshint": "^0.10.0",
19 | "grunt-contrib-uglify": "^0.4.0",
20 | "grunt-contrib-watch": "^0.6.1",
21 | "grunt-filerev": "^0.2.1",
22 | "grunt-google-cdn": "^0.4.0",
23 | "grunt-newer": "^0.7.0",
24 | "grunt-ng-annotate": "^0.4.0",
25 | "grunt-ng-constant": "^1.0.0",
26 | "grunt-svgmin": "^0.4.0",
27 | "grunt-usemin": "^2.1.1",
28 | "grunt-wiredep": "^1.7.0",
29 | "jshint-stylish": "^0.2.0",
30 | "load-grunt-tasks": "^0.4.0",
31 | "time-grunt": "^0.3.1"
32 | },
33 | "engines": {
34 | "node": ">=0.10.0"
35 | },
36 | "scripts": {
37 | "test": "grunt test"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------