From 7b21222bcba3256e2c53e7274e6ed6581022bbde Mon Sep 17 00:00:00 2001 From: Ruan Date: Mon, 26 Nov 2018 10:08:30 -0300 Subject: [PATCH] Aula 3 - OneBitCode --- Gemfile | 3 + Gemfile.lock | 3 + app/assets/javascripts/application.js | 1 + app/assets/javascripts/favorites.coffee | 3 + app/assets/javascripts/manifest.json.erb | 19 ++++++ app/assets/javascripts/player.js | 61 +++++++++++++++++ app/assets/javascripts/recently_heards.coffee | 3 + .../javascripts/serviceworker-companion.js | 6 ++ app/assets/javascripts/serviceworker.js.erb | 64 ++++++++++++++++++ app/assets/stylesheets/application.scss | 2 +- app/assets/stylesheets/favorites.scss | 3 + app/assets/stylesheets/recently_heards.scss | 3 + app/assets/stylesheets/shared/_tab.scss | 7 +- app/assets/stylesheets/songs/_song.scss | 5 +- app/controllers/dashboard_controller.rb | 2 +- app/controllers/favorites_controller.rb | 17 +++++ app/controllers/recently_heards_controller.rb | 7 ++ app/helpers/favorites_helper.rb | 18 +++++ app/helpers/recently_heards_helper.rb | 2 + app/views/albums/show.html.erb | 8 ++- app/views/favorites/create.js.erb | 5 ++ app/views/favorites/destroy.js.erb | 5 ++ app/views/favorites/index.html.erb | 6 ++ app/views/layouts/application.html.erb | 3 + app/views/recently_heards/create.html.erb | 2 + app/views/shared/_toolbar.html.erb | 2 +- app/views/songs/_song.html.erb | 5 +- config/initializers/assets.rb | 1 + config/initializers/serviceworker.rb | 26 ++++++++ config/routes.rb | 11 +++- public/offline.html | 66 +++++++++++++++++++ test/controllers/favorites_controller_test.rb | 19 ++++++ .../recently_heards_controller_test.rb | 9 +++ 33 files changed, 386 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/favorites.coffee create mode 100644 app/assets/javascripts/manifest.json.erb create mode 100644 app/assets/javascripts/player.js create mode 100644 app/assets/javascripts/recently_heards.coffee create mode 100644 app/assets/javascripts/serviceworker-companion.js create mode 100644 app/assets/javascripts/serviceworker.js.erb create mode 100644 app/assets/stylesheets/favorites.scss create mode 100644 app/assets/stylesheets/recently_heards.scss create mode 100644 app/controllers/favorites_controller.rb create mode 100644 app/controllers/recently_heards_controller.rb create mode 100644 app/helpers/favorites_helper.rb create mode 100644 app/helpers/recently_heards_helper.rb create mode 100644 app/views/favorites/create.js.erb create mode 100644 app/views/favorites/destroy.js.erb create mode 100644 app/views/favorites/index.html.erb create mode 100644 app/views/recently_heards/create.html.erb create mode 100644 config/initializers/serviceworker.rb create mode 100644 public/offline.html create mode 100644 test/controllers/favorites_controller_test.rb create mode 100644 test/controllers/recently_heards_controller_test.rb diff --git a/Gemfile b/Gemfile index f7326d6..01be7ff 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,9 @@ gem 'devise', '~> 4.5.0' # Progressive Web Apps for Rails gem 'pwa', '~> 4.0.5' +# Transforma o app em um PWA +gem "serviceworker-rails" + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index c4712d2..0a9f09f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,6 +178,8 @@ GEM selenium-webdriver (3.141.0) childprocess (~> 0.5) rubyzip (~> 1.2, >= 1.2.2) + serviceworker-rails (0.5.5) + railties (>= 3.1) spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -235,6 +237,7 @@ DEPENDENCIES rails (~> 5.2.1) sass-rails (~> 5.0) selenium-webdriver + serviceworker-rails spring spring-watcher-listen (~> 2.0.0) sqlite3 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 241aec9..db283aa 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,3 +15,4 @@ //= require turbolinks //= require jquery/dist/jquery.min //= require_tree . +//= require serviceworker-companion diff --git a/app/assets/javascripts/favorites.coffee b/app/assets/javascripts/favorites.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/favorites.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/manifest.json.erb b/app/assets/javascripts/manifest.json.erb new file mode 100644 index 0000000..752b9db --- /dev/null +++ b/app/assets/javascripts/manifest.json.erb @@ -0,0 +1,19 @@ +<% icon_sizes = Rails.configuration.serviceworker.icon_sizes %> +{ + "name": "Spotcode - Música na hora", + "short_name": "Spotcode", + "start_url": "/", + "icons": [ + <% icon_sizes.map { |s| "#{s}x#{s}" }.each.with_index do |dim, i| %> + { + "src": "<%= image_path "serviceworker-rails/heart-#{dim}.png" %>", + "sizes": "<%= dim %>", + "type": "image/png" + }<%= i == (icon_sizes.length - 1) ? '' : ',' %> + <% end %> + ], + "theme_color": "#000000", + "background_color": "#FFFFFF", + "display": "fullscreen", + "orientation": "portrait" +} diff --git a/app/assets/javascripts/player.js b/app/assets/javascripts/player.js new file mode 100644 index 0000000..d4f6308 --- /dev/null +++ b/app/assets/javascripts/player.js @@ -0,0 +1,61 @@ +$(document).on("turbolinks:load", function(){ + const player = $("audio#song-player"); + let currentSong = null; + + $("div.play-button a").click(function(){ + button = $(this); + if ($(this).closest("div.song-item").is(currentSong)) { + if (player[0].paused) { + resumeSong(button); + } else { + pauseSong(button) + } + } else { + playNewSong(button); + } + }); + + $("button#play-all").click(function(){ + button = $("div.song-item:eq(0) div.play-button a"); + playNewSong(button); + }); + + player.on('ended', function(){ + playing = $("div.song-item.playing"); + next = playing.next(); + if (next.length > 0) { + play_button = $(next[0]).find('div.play-button a'); + playNewSong(play_button); + } + }); + + function playNewSong(button) { + $("div.song-item").removeClass("playing"); + $(button).closest("div.song-item").addClass("playing"); + $("div.song-item div.play-button i").removeClass("fa-pause-circle").addClass("fa-play-circle"); + player.prop("src", $(button).data("song")); + resumeSong(button); + sendRecentlyHeard(button); + currentSong = button.closest("div.song-item"); + } + + function pauseSong(button) { + $(button).children().filter("div.play-button i").removeClass("fa-pause-circle").addClass("fa-play-circle"); + player[0].pause(); + } + + function resumeSong(button) { + $(button).children().filter("i").removeClass("fa-play-circle").addClass("fa-pause-circle"); + player[0].play(); + } + + function sendRecentlyHeard(button) { + token = $('meta[name="csrf-token"]').attr('content'); + $.ajax({ + type: "POST", + url: button.data('url'), + headers: { 'X-CSRF-Token': token }, + success: () => {} + }); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/recently_heards.coffee b/app/assets/javascripts/recently_heards.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/recently_heards.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/serviceworker-companion.js b/app/assets/javascripts/serviceworker-companion.js new file mode 100644 index 0000000..8a524aa --- /dev/null +++ b/app/assets/javascripts/serviceworker-companion.js @@ -0,0 +1,6 @@ +if (navigator.serviceWorker) { + navigator.serviceWorker.register('/serviceworker.js', { scope: './' }) + .then(function(reg) { + console.log('[Companion]', 'Service worker registered!'); + }); +} diff --git a/app/assets/javascripts/serviceworker.js.erb b/app/assets/javascripts/serviceworker.js.erb new file mode 100644 index 0000000..c5e5738 --- /dev/null +++ b/app/assets/javascripts/serviceworker.js.erb @@ -0,0 +1,64 @@ +var CACHE_VERSION = 'v1'; +var CACHE_NAME = CACHE_VERSION + ':sw-cache-'; + +function onInstall(event) { + console.log('[Serviceworker]', "Installing!", event); + event.waitUntil( + caches.open(CACHE_NAME).then(function prefill(cache) { + return cache.addAll([ + + // make sure serviceworker.js is not required by application.js + // if you want to reference application.js from here + '<%#= asset_path "application.js" %>', + + '<%= asset_path "application.css" %>', + + '/offline.html', + + ]); + }) + ); +} + +function onActivate(event) { + console.log('[Serviceworker]', "Activating!", event); + event.waitUntil( + caches.keys().then(function(cacheNames) { + return Promise.all( + cacheNames.filter(function(cacheName) { + // Return true if you want to remove this cache, + // but remember that caches are shared across + // the whole origin + return cacheName.indexOf(CACHE_VERSION) !== 0; + }).map(function(cacheName) { + return caches.delete(cacheName); + }) + ); + }) + ); +} + +// Borrowed from https://github.com/TalAter/UpUp +function onFetch(event) { + event.respondWith( + // try to return untouched request from network first + fetch(event.request).catch(function() { + // if it fails, try to return request from the cache + return caches.match(event.request).then(function(response) { + if (response) { + return response; + } + // if not found in cache, return default offline content for navigate requests + if (event.request.mode === 'navigate' || + (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) { + console.log('[Serviceworker]', "Fetching offline content", event); + return caches.match('/offline.html'); + } + }) + }) + ); +} + +self.addEventListener('install', onInstall); +self.addEventListener('activate', onActivate); +self.addEventListener('fetch', onFetch); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 99b97df..8134220 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -10,8 +10,8 @@ * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * - *= require_tree . *= require_self + *= require_tree . */ @import "bulma/bulma"; diff --git a/app/assets/stylesheets/favorites.scss b/app/assets/stylesheets/favorites.scss new file mode 100644 index 0000000..819decf --- /dev/null +++ b/app/assets/stylesheets/favorites.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the favorites controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/recently_heards.scss b/app/assets/stylesheets/recently_heards.scss new file mode 100644 index 0000000..5176ebc --- /dev/null +++ b/app/assets/stylesheets/recently_heards.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the recently_heards controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/shared/_tab.scss b/app/assets/stylesheets/shared/_tab.scss index c5b244c..4703204 100644 --- a/app/assets/stylesheets/shared/_tab.scss +++ b/app/assets/stylesheets/shared/_tab.scss @@ -1,14 +1,11 @@ div.tabs.shared { li { a { - //color: $white; color: white; } &.is-active { a { - //color: $primary; - //border-bottom-color: $primary; color: green; border-bottom-color: green; } @@ -23,5 +20,9 @@ div.tabs-content { &.active { display: block; } + + button#play-all{ + margin-bottom: 15px; + } } } \ No newline at end of file diff --git a/app/assets/stylesheets/songs/_song.scss b/app/assets/stylesheets/songs/_song.scss index 029f7b2..377600f 100644 --- a/app/assets/stylesheets/songs/_song.scss +++ b/app/assets/stylesheets/songs/_song.scss @@ -1,11 +1,14 @@ div.song-item { - //border-top: 1px solid $white; border-top: 1px solid white; div.play-button { margin-top: 12px; } + div.favorite { + margin-top: 10px; + } + &:first-child { border-top: none; } diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 1c32e28..368988a 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -6,7 +6,7 @@ def index private def load_recent_heard - @recent_albums = current_user.recently_heards.order("created_at DESC").limit(4).map(&:album) + @recent_albums = current_user.recently_heards.order("created_at DESC").group(:album_id).limit(4).map(&:album) end def load_recommendations diff --git a/app/controllers/favorites_controller.rb b/app/controllers/favorites_controller.rb new file mode 100644 index 0000000..66cf38b --- /dev/null +++ b/app/controllers/favorites_controller.rb @@ -0,0 +1,17 @@ +class FavoritesController < ApplicationController + def index + @favorite_albums = current_user.favorites.where(favoritable_type: "Album").map(&:favoritable) + @favorite_songs = current_user.favorites.where(favoritable_type: "Song").map(&:favoritable) + @favorite_artists = current_user.favorites.where(favoritable_type: "Artist").map(&:favoritable) + end + + def create + @favoritable = current_user.favorites.new(favoritable_type: params[:favoritable_type], favoritable_id: params[:id]) + @favoritable.save + end + + def destroy + @favoritable = current_user.favorites.find_by(favoritable_type: params[:favoritable_type], favoritable_id: params[:id]) + @favoritable.destroy + end +end \ No newline at end of file diff --git a/app/controllers/recently_heards_controller.rb b/app/controllers/recently_heards_controller.rb new file mode 100644 index 0000000..efc6cb4 --- /dev/null +++ b/app/controllers/recently_heards_controller.rb @@ -0,0 +1,7 @@ +class RecentlyHeardsController < ApplicationController + def create + @recently_heard = current_user.recently_heards.new(album_id: params[:album_id]) + @recently_heard.save + head :ok + end +end diff --git a/app/helpers/favorites_helper.rb b/app/helpers/favorites_helper.rb new file mode 100644 index 0000000..45016d5 --- /dev/null +++ b/app/helpers/favorites_helper.rb @@ -0,0 +1,18 @@ +module FavoritesHelper + def favorite_button(favoritable) + if current_user.favorites.where(favoritable: favoritable).exists? + render_button(favoritable.id, favoritable.class, :delete, :fas) + else + render_button(favoritable.id, favoritable.class, :post, :far) + end + end + + + private + def render_button(id, type, method, icon_format) + url = send("favorite_#{type.to_s.downcase}_path", id) + link_to url, class: "has-text-white", data: { remote: true, method: method, kind: type.to_s, id: id } do + content_tag :i, "", class: "#{icon_format} fa-heart fa-2x" + end + end +end \ No newline at end of file diff --git a/app/helpers/recently_heards_helper.rb b/app/helpers/recently_heards_helper.rb new file mode 100644 index 0000000..5775057 --- /dev/null +++ b/app/helpers/recently_heards_helper.rb @@ -0,0 +1,2 @@ +module RecentlyHeardsHelper +end diff --git a/app/views/albums/show.html.erb b/app/views/albums/show.html.erb index 09f8b8f..2cff126 100644 --- a/app/views/albums/show.html.erb +++ b/app/views/albums/show.html.erb @@ -10,7 +10,13 @@

Músicas


-
+
+
+ +
<%= render @album.songs %>
\ No newline at end of file diff --git a/app/views/favorites/create.js.erb b/app/views/favorites/create.js.erb new file mode 100644 index 0000000..2afd3d8 --- /dev/null +++ b/app/views/favorites/create.js.erb @@ -0,0 +1,5 @@ +link_filter = "a[data-kind='<%= @favoritable.favoritable_type %>'][data-id='<%= @favoritable.favoritable_id %>']"; + +$('${link_filter}').attr('data-method', 'delete'); + +$('${link_filter} i').removeClass('far').addClass('fas'); \ No newline at end of file diff --git a/app/views/favorites/destroy.js.erb b/app/views/favorites/destroy.js.erb new file mode 100644 index 0000000..eaf7e53 --- /dev/null +++ b/app/views/favorites/destroy.js.erb @@ -0,0 +1,5 @@ +link_filter = "a[data-kind='<%= @favoritable.favoritable_type %>'][data-id='<%= @favoritable.favoritable_id %>']"; + +$('${link_filter}').attr('data-method', 'post'); + +$('${link_filter} i').removeClass('fas').addClass('far'); diff --git a/app/views/favorites/index.html.erb b/app/views/favorites/index.html.erb new file mode 100644 index 0000000..2c5d0b8 --- /dev/null +++ b/app/views/favorites/index.html.erb @@ -0,0 +1,6 @@ +
+

Favoritos

+
+ + <%= render "shared/tabs", artists: @favorite_artists, songs: @favorite_songs, albums: @favorite_albums %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 02f1637..b2a235d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,11 +8,14 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + <%= render "shared/menu" %> <%= yield %> + <%= render "shared/toolbar" %> \ No newline at end of file diff --git a/app/views/recently_heards/create.html.erb b/app/views/recently_heards/create.html.erb new file mode 100644 index 0000000..387dc87 --- /dev/null +++ b/app/views/recently_heards/create.html.erb @@ -0,0 +1,2 @@ +

RecentlyHeards#create

+

Find me in app/views/recently_heards/create.html.erb

diff --git a/app/views/shared/_toolbar.html.erb b/app/views/shared/_toolbar.html.erb index 6db57cd..70ce7d4 100644 --- a/app/views/shared/_toolbar.html.erb +++ b/app/views/shared/_toolbar.html.erb @@ -13,7 +13,7 @@
- <%= link_to "", class: "has-text-white" do %> + <%= link_to favorites_path, class: "has-text-white" do %> <% end %>
diff --git a/app/views/songs/_song.html.erb b/app/views/songs/_song.html.erb index 354dd5d..beabcb9 100644 --- a/app/views/songs/_song.html.erb +++ b/app/views/songs/_song.html.erb @@ -1,7 +1,7 @@
- <%= link_to "", class: "has-text-white" do %> + <%= link_to "javascript:void(0);", class: "has-text-white", data: { song: url_for(song.file), url: album_recently_heards_path(song.album) } do %> <% end %>
@@ -9,5 +9,8 @@

<%= song.title %>

<%= song.album.artist.name %>

+
+ <%= favorite_button(song) %> +
\ No newline at end of file diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 4b828e8..a98ce9a 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -12,3 +12,4 @@ # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. # Rails.application.config.assets.precompile += %w( admin.js admin.css ) +Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json] diff --git a/config/initializers/serviceworker.rb b/config/initializers/serviceworker.rb new file mode 100644 index 0000000..b21812c --- /dev/null +++ b/config/initializers/serviceworker.rb @@ -0,0 +1,26 @@ +Rails.application.configure do + config.serviceworker.routes.draw do + # map to assets implicitly + match "/serviceworker.js" + match "/manifest.json" + + # Examples + # + # map to a named asset explicitly + # match "/proxied-serviceworker.js" => "nested/asset/serviceworker.js" + # match "/nested/serviceworker.js" => "another/serviceworker.js" + # + # capture named path segments and interpolate to asset name + # match "/captures/*segments/serviceworker.js" => "%{segments}/serviceworker.js" + # + # capture named parameter and interpolate to asset name + # match "/parameter/:id/serviceworker.js" => "project/%{id}/serviceworker.js" + # + # insert custom headers + # match "/header-serviceworker.js" => "another/serviceworker.js", + # headers: { "X-Resource-Header" => "A resource" } + # + # anonymous glob exposes `paths` variable for interpolation + # match "/*/serviceworker.js" => "%{paths}/serviceworker.js" + end +end diff --git a/config/routes.rb b/config/routes.rb index 08157b5..f5f75ae 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,11 +2,18 @@ devise_for :users authenticated :user do - root to: "dashboard#index", as: :authenticated_root + resources :favorites, only: :index resources :search, only: [:index, :new], as: :searches resources :categories, only: :show resources :artists, only: :show - resources :albums, only: :show + resources :albums, only: :show do + resources :recently_heards, only: :create + end + resources :songs, only: [] do + post "/favorite", to: "favorites#create", on: :member, defaults: { format: :js, favoritable_type: 'Song' } + delete "/favorite", to: "favorites#destroy", on: :member, defaults: { format: :js, favoritable_type: 'Song' } + end + root to: "dashboard#index", as: :authenticated_root end unauthenticated :user do diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..2016c7b --- /dev/null +++ b/public/offline.html @@ -0,0 +1,66 @@ + + + + You are not connected to the Internet + + + + + + +
+
+

It looks like you've lost your Internet connection

+

You may need to reconnect to Wi-Fi.

+
+
+ + diff --git a/test/controllers/favorites_controller_test.rb b/test/controllers/favorites_controller_test.rb new file mode 100644 index 0000000..b6ad8c5 --- /dev/null +++ b/test/controllers/favorites_controller_test.rb @@ -0,0 +1,19 @@ +require 'test_helper' + +class FavoritesControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get favorites_index_url + assert_response :success + end + + test "should get create" do + get favorites_create_url + assert_response :success + end + + test "should get destroy" do + get favorites_destroy_url + assert_response :success + end + +end diff --git a/test/controllers/recently_heards_controller_test.rb b/test/controllers/recently_heards_controller_test.rb new file mode 100644 index 0000000..21ee6c5 --- /dev/null +++ b/test/controllers/recently_heards_controller_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class RecentlyHeardsControllerTest < ActionDispatch::IntegrationTest + test "should get create" do + get recently_heards_create_url + assert_response :success + end + +end