Merge branch 'master' into translations_en-yml--master_de_DE
| @@ -31,42 +31,33 @@ $(document).on('turbolinks:load', function(){ | ||||
|       $("#delete-confirm").parent().attr("action", url) | ||||
|     }) | ||||
|  | ||||
|     // Change the color of the color inputs when the color is changed | ||||
|     $(".colorinput-input").change(function(data) { | ||||
|       // Get the color from the input | ||||
|       var color = $(data.target).val() | ||||
|     $('.colorinput').ColorPicker({ | ||||
|       onHide: function (colpkr) { | ||||
|         var colour = $("#user-colour").val(); | ||||
|  | ||||
|         // Update the color in the database and reload the page | ||||
|       $.post($("#coloring-path").val(), {color: color}).done(function(data) { | ||||
|         $.post($("#coloring-path").val(), {color: colour}).done(function(data) { | ||||
|           location.reload() | ||||
|         }); | ||||
|       }, | ||||
|  | ||||
|       onSubmit: function(hsb, hex, rgb, el) { | ||||
|         $.post($("#coloring-path").val(), {color: '#' + hex}).done(function(data) { | ||||
|           location.reload() | ||||
|         }); | ||||
|       }, | ||||
|        | ||||
|     // Submit search if the user hits enter | ||||
|     $("#search-input").keypress(function(key) { | ||||
|       var keyPressed = key.which | ||||
|       if (keyPressed == 13) { | ||||
|         searchPage() | ||||
|       } | ||||
|     }) | ||||
|       onBeforeShow: function () { | ||||
|         var colour = $("#user-colour").val(); | ||||
|  | ||||
|     // Add listeners for sort | ||||
|     $("th[data-order]").click(function(data){ | ||||
|       var header_elem = $(data.target) | ||||
|         $(this).ColorPickerSetColor(colour); | ||||
|       }, | ||||
|  | ||||
|       if(header_elem.data('order') === 'asc'){ // asc | ||||
|         header_elem.data('order', 'desc'); | ||||
|       onChange: function (hsb, hex, rgb) { | ||||
|         $('.colorinput span').css('backgroundColor', '#' + hex); | ||||
|         $("#user-colour").val('#' + hex); | ||||
|       } | ||||
|       else if(header_elem.data('order') === 'desc'){ // desc | ||||
|         header_elem.data('order', 'none'); | ||||
|       } | ||||
|       else{ // none | ||||
|         header_elem.data('order', 'asc'); | ||||
|       } | ||||
|  | ||||
|       var search = $("#search-input").val() | ||||
|       window.location.replace(window.location.pathname + "?page=1&search=" + search + "&column=" + header_elem.data("header") + "&direction="+ header_elem.data('order')) | ||||
|     }) | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Only run on the admins edit user page. | ||||
| @@ -76,8 +67,8 @@ $(document).on('turbolinks:load', function(){ | ||||
|       if (!url.endsWith("/")) { | ||||
|         url += "/" | ||||
|       } | ||||
|  | ||||
|       url += "admins?setting=" + data.target.id | ||||
|  | ||||
|       window.location.href = url | ||||
|     }) | ||||
|   } | ||||
| @@ -88,15 +79,3 @@ function changeBrandingImage(path) { | ||||
|   var url = $("#branding-url").val() | ||||
|   $.post(path, {url: url}) | ||||
| } | ||||
|  | ||||
| // Searches the user table for the given string | ||||
| function searchPage() { | ||||
|   var search = $("#search-input").val() | ||||
|  | ||||
|   window.location.replace(window.location.pathname + "?page=1&search=" + search) | ||||
| } | ||||
|  | ||||
| // Clears the search bar | ||||
| function clearSearch() { | ||||
|   window.location.replace(window.location.pathname + "?page=1") | ||||
| } | ||||
|   | ||||
| @@ -31,4 +31,5 @@ | ||||
| //= require tabler | ||||
| //= require tabler.plugins | ||||
| //= require jquery_ujs | ||||
| //= require colorpicker | ||||
| //= require_tree . | ||||
|   | ||||
| @@ -32,10 +32,10 @@ $(document).on('turbolinks:load', function(){ | ||||
|       if (success) { | ||||
|         inviteURL.blur(); | ||||
|         copy.addClass('btn-success'); | ||||
|         copy.html("<i class='fas fa-check'></i> Copy") | ||||
|         copy.html("<i class='fas fa-check'></i> <%= I18n.t("copied") %>") | ||||
|         setTimeout(function(){ | ||||
|           copy.removeClass('btn-success'); | ||||
|           copy.html("<i class='fas fa-copy'></i> Copy") | ||||
|           copy.html("<i class='fas fa-copy'></i> <%= I18n.t("copy") %>") | ||||
|         }, 2000) | ||||
|       } | ||||
|     }); | ||||
| @@ -18,36 +18,81 @@ $(document).on('turbolinks:load', function(){ | ||||
|   var controller = $("body").data('controller'); | ||||
|   var action = $("body").data('action'); | ||||
|  | ||||
|   if(controller == "rooms" && action == "show" || controller == "rooms" && action == "update"){ | ||||
|     var search_input = $('#search_bar'); | ||||
|   if ((controller == "admins" && action == "index") ||  | ||||
|       (controller == "rooms" && action == "show") ||  | ||||
|       (controller == "rooms" && action == "update") || | ||||
|       (controller == "rooms" && action == "join") ||  | ||||
|       (controller == "users" && action == "recordings")) { | ||||
|     // Submit search if the user hits enter | ||||
|     $("#search-input").keypress(function(key) { | ||||
|       var keyPressed = key.which | ||||
|       if (keyPressed == 13) { | ||||
|         searchPage() | ||||
|       } | ||||
|     }) | ||||
|  | ||||
|     search_input.bind("keyup", function(){ | ||||
|     // Add listeners for sort | ||||
|     $("th[data-order]").click(function(data){ | ||||
|       var header_elem = $(data.target) | ||||
|       var controller = $("body").data('controller'); | ||||
|       var action = $("body").data('action'); | ||||
|  | ||||
|       // Retrieve the current search query | ||||
|       var search_query = search_input.find(".form-control").val(); | ||||
|       if(header_elem.data('order') === 'asc'){ // asc | ||||
|         header_elem.data('order', 'desc'); | ||||
|       } | ||||
|       else if(header_elem.data('order') === 'desc'){ // desc | ||||
|         header_elem.data('order', 'none'); | ||||
|       } | ||||
|       else{ // none | ||||
|         header_elem.data('order', 'asc'); | ||||
|       } | ||||
|  | ||||
|       //Search for recordings and display them based on name match | ||||
|       var recordings_found = 0; | ||||
|       var search = $("#search-input").val(); | ||||
|  | ||||
|       var recordings = $('#recording-table').find('tr'); | ||||
|  | ||||
|       recordings.each(function(){ | ||||
|         if($(this).find('text').text().toLowerCase().includes(search_query.toLowerCase())){ | ||||
|           recordings_found = recordings_found + 1; | ||||
|           $(this).show(); | ||||
|       if(controller === "rooms" && action === "show"){ | ||||
|         window.location.replace(window.location.pathname + "?page=1&search=" + search +  | ||||
|           "&column=" + header_elem.data("header") + "&direction="+ header_elem.data('order') +  | ||||
|           "#recordings-table"); | ||||
|       } | ||||
|       else{ | ||||
|           $(this).hide(); | ||||
|         window.location.replace(window.location.pathname + "?page=1&search=" + search +  | ||||
|           "&column=" + header_elem.data("header") + "&direction="+ header_elem.data('order')); | ||||
|       } | ||||
|       }); | ||||
|     }) | ||||
|  | ||||
|       // Show "No recordings match your search" if no recordings found | ||||
|       if(recordings_found === 0){ | ||||
|         $('#no_recordings_found').show(); | ||||
|     if(controller === "rooms" && action === "show"){ | ||||
|       $(".page-item > a").each(function(){ | ||||
|         if(!$(this).attr('href').endsWith("#")){ | ||||
|           $(this).attr('href', $(this).attr('href') + "#recordings-table") | ||||
|         } | ||||
|       else{ | ||||
|         $('#no_recordings_found').hide(); | ||||
|       }) | ||||
|     } | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
| }) | ||||
|  | ||||
| // Searches the user table for the given string | ||||
| function searchPage() { | ||||
|   var search = $("#search-input").val(); | ||||
|  | ||||
|   var controller = $("body").data('controller'); | ||||
|   var action = $("body").data('action'); | ||||
|  | ||||
|   if(controller === "rooms" && action === "show"){ | ||||
|     window.location.replace(window.location.pathname + "?page=1&search=" + search + "#recordings-table"); | ||||
|   } else{ | ||||
|     window.location.replace(window.location.pathname + "?page=1&search=" + search); | ||||
|   } | ||||
|    | ||||
| } | ||||
|  | ||||
| // Clears the search bar | ||||
| function clearSearch() { | ||||
|   var controller = $("body").data('controller'); | ||||
|   var action = $("body").data('action'); | ||||
|  | ||||
|   if(controller === "rooms" && action === "show"){ | ||||
|     window.location.replace(window.location.pathname + "?page=1"  + "#recordings-table"); | ||||
|   } else{ | ||||
|     window.location.replace(window.location.pathname + "?page=1"); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -30,3 +30,7 @@ | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
|  | ||||
| #branding-image{ | ||||
|   z-index: auto; | ||||
| } | ||||
|   | ||||
| @@ -33,6 +33,7 @@ | ||||
| @import "tabler/variables"; | ||||
| @import "bootstrap"; | ||||
| @import "tabler-custom"; | ||||
| @import "colorpicker"; | ||||
|  | ||||
| @import "utilities/variables"; | ||||
| @import "admins"; | ||||
| @@ -105,7 +106,7 @@ a { | ||||
| } | ||||
|  | ||||
| .invite-link-input { | ||||
|   width: 45%; | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .no-border-top { | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
|  | ||||
| class RoomsController < ApplicationController | ||||
|   include RecordingsHelper | ||||
|   include Pagy::Backend | ||||
|  | ||||
|   before_action :validate_accepted_terms, unless: -> { !Rails.configuration.terms } | ||||
|   before_action :validate_verified_email, except: [:show, :join], | ||||
| @@ -52,9 +53,11 @@ class RoomsController < ApplicationController | ||||
|   # GET /:room_uid | ||||
|   def show | ||||
|     if current_user && @room.owned_by?(current_user) | ||||
|       recs = @room.recordings | ||||
|       @search, @order_column, @order_direction, recs = | ||||
|         @room.recordings(params.permit(:search, :column, :direction), true) | ||||
|  | ||||
|       @pagy, @recordings = pagy_array(recs) | ||||
|  | ||||
|       @recordings = recs | ||||
|       @is_running = @room.running? | ||||
|     else | ||||
|       # Get users name | ||||
| @@ -66,6 +69,11 @@ class RoomsController < ApplicationController | ||||
|         "" | ||||
|       end | ||||
|  | ||||
|       @search, @order_column, @order_direction, pub_recs = | ||||
|         @room.public_recordings(params.permit(:search, :column, :direction), true) | ||||
|  | ||||
|       @pagy, @public_recordings = pagy_array(pub_recs) | ||||
|  | ||||
|       render :join | ||||
|     end | ||||
|   end | ||||
| @@ -119,6 +127,13 @@ class RoomsController < ApplicationController | ||||
|         redirect_to @room.join_path(join_name, opts) | ||||
|       end | ||||
|     else | ||||
|  | ||||
|       search_params = params[@room.invite_path] || params | ||||
|       @search, @order_column, @order_direction, pub_recs = | ||||
|         @room.public_recordings(search_params.permit(:search, :column, :direction), true) | ||||
|  | ||||
|       @pagy, @public_recordings = pagy_array(pub_recs) | ||||
|  | ||||
|       # They need to wait until the meeting begins. | ||||
|       render :wait | ||||
|     end | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
|  | ||||
| class UsersController < ApplicationController | ||||
|   include RecordingsHelper | ||||
|   include Pagy::Backend | ||||
|   include Emailer | ||||
|  | ||||
|   before_action :find_user, only: [:edit, :update, :destroy] | ||||
| @@ -141,7 +142,9 @@ class UsersController < ApplicationController | ||||
|   # GET /u/:user_uid/recordings | ||||
|   def recordings | ||||
|     if current_user && current_user.uid == params[:user_uid] | ||||
|       @recordings = current_user.all_recordings | ||||
|       @search, @order_column, @order_direction, recs = | ||||
|         current_user.all_recordings(params.permit(:search, :column, :direction), true) | ||||
|       @pagy, @recordings = pagy_array(recs) | ||||
|     else | ||||
|       redirect_to root_path | ||||
|     end | ||||
|   | ||||
| @@ -78,8 +78,6 @@ module ApplicationHelper | ||||
|   def allow_greenlight_accounts? | ||||
|     return Rails.configuration.allow_user_signup unless Rails.configuration.loadbalanced_configuration | ||||
|     return false unless @user_domain && !@user_domain.empty? && Rails.configuration.allow_user_signup | ||||
|     # No need to retrieve the provider info if the provider is whitelisted | ||||
|     return true if launcher_allow_user_signup_whitelisted?(@user_domain) | ||||
|     # Proceed with retrieving the provider info | ||||
|     begin | ||||
|       provider_info = retrieve_provider_info(@user_domain, 'api2', 'getUserGreenlightCredentials') | ||||
|   | ||||
| @@ -17,6 +17,8 @@ | ||||
| # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| module RecordingsHelper | ||||
|   include Pagy::Frontend | ||||
|  | ||||
|   # Helper for converting BigBlueButton dates into the desired format. | ||||
|   def recording_date(date) | ||||
|     date.strftime("%B #{date.day.ordinalize}, %Y.") | ||||
|   | ||||
| @@ -17,4 +17,9 @@ | ||||
| # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| module RoomsHelper | ||||
|   # Helper to generate the path to a Google Calendar event creation | ||||
|   # It will have its title set as the room name, and the location as the URL to the room | ||||
|   def google_calendar_path | ||||
|     "http://calendar.google.com/calendar/r/eventedit?text=#{@room.name}&location=#{request.base_url + request.fullpath}" | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -19,8 +19,14 @@ | ||||
| module APIConcern | ||||
|   extend ActiveSupport::Concern | ||||
|  | ||||
|   # Format recordings to match their current use in the app | ||||
|   def format_recordings(api_res) | ||||
|   # Format, filter, and sort recordings to match their current use in the app | ||||
|   def format_recordings(api_res, search_params, ret_search_params) | ||||
|     search = search_params[:search] || "" | ||||
|     order_col = search_params[:column] && search_params[:direction] != "none" ? search_params[:column] : "end_time" | ||||
|     order_dir = search_params[:column] && search_params[:direction] != "none" ? search_params[:direction] : "asc" | ||||
|  | ||||
|     search = search.downcase | ||||
|  | ||||
|     api_res[:recordings].each do |r| | ||||
|       next if r.key?(:error) | ||||
|       # Format playbacks in a more pleasant way. | ||||
| @@ -34,6 +40,57 @@ module APIConcern | ||||
|       r.delete(:playback) | ||||
|     end | ||||
|  | ||||
|     api_res[:recordings].sort_by { |rec| rec[:endTime] }.reverse | ||||
|     recs = filter_recordings(api_res, search) | ||||
|     recs = sort_recordings(recs, order_col, order_dir) | ||||
|  | ||||
|     if ret_search_params | ||||
|       [search, order_col, order_dir, recs] | ||||
|     else | ||||
|       recs | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def filter_recordings(api_res, search) | ||||
|     api_res[:recordings].select do |r| | ||||
|              (!r[:metadata].nil? && ((!r[:metadata][:name].nil? && | ||||
|                     r[:metadata][:name].downcase.include?(search)) || | ||||
|                   (r[:metadata][:"gl-listed"] == "true" && search == "public") || | ||||
|                   (r[:metadata][:"gl-listed"] == "false" && search == "unlisted"))) || | ||||
|                ((r[:metadata].nil? || r[:metadata][:name].nil?) && | ||||
|                  r[:name].downcase.include?(search)) || | ||||
|                r[:participants].include?(search) || | ||||
|                !r[:playbacks].select { |p| p[:type].downcase.include?(search) }.empty? | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def sort_recordings(recs, order_col, order_dir) | ||||
|     recs = case order_col | ||||
|            when "end_time" | ||||
|         recs.sort_by { |r| r[:endTime] } | ||||
|            when "name" | ||||
|         recs.sort_by do |r| | ||||
|           if !r[:metadata].nil? && !r[:metadata][:name].nil? | ||||
|             r[:metadata][:name].downcase | ||||
|           else | ||||
|             r[:name].downcase | ||||
|           end | ||||
|         end | ||||
|            when "length" | ||||
|         recs.sort_by { |r| r[:playbacks].reject { |p| p[:type] == "statistics" }.first[:length] } | ||||
|            when "users" | ||||
|         recs.sort_by { |r| r[:participants] } | ||||
|            when "visibility" | ||||
|         recs.sort_by { |r| r[:metadata][:"gl-listed"] } | ||||
|            when "formats" | ||||
|         recs.sort_by { |r| r[:playbacks].first[:type].downcase } | ||||
|       else | ||||
|         recs.sort_by { |r| r[:endTime] } | ||||
|     end | ||||
|  | ||||
|     if order_dir == 'asc' | ||||
|       recs | ||||
|     else | ||||
|       recs.reverse | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -121,15 +121,16 @@ class Room < ApplicationRecord | ||||
|   end | ||||
|  | ||||
|   # Fetches all recordings for a room. | ||||
|   def recordings | ||||
|   def recordings(search_params = {}, ret_search_params = false) | ||||
|     res = bbb.get_recordings(meetingID: bbb_id) | ||||
|  | ||||
|     format_recordings(res) | ||||
|     format_recordings(res, search_params, ret_search_params) | ||||
|   end | ||||
|  | ||||
|   # Fetches a rooms public recordings. | ||||
|   def public_recordings | ||||
|     recordings.select { |r| r[:metadata][:"gl-listed"] == "true" } | ||||
|   def public_recordings(search_params = {}, ret_search_params = false) | ||||
|     search, order_col, order_dir, recs = recordings(search_params, ret_search_params) | ||||
|     [search, order_col, order_dir, recs.select { |r| r[:metadata][:"gl-listed"] == "true" }] | ||||
|   end | ||||
|  | ||||
|   def update_recording(record_id, meta) | ||||
|   | ||||
| @@ -121,7 +121,7 @@ class User < ApplicationRecord | ||||
|     order("#{column} #{direction}") | ||||
|   end | ||||
|  | ||||
|   def all_recordings | ||||
|   def all_recordings(search_params = {}, ret_search_params = false) | ||||
|     pag_num = Rails.configuration.pagination_number | ||||
|  | ||||
|     pag_loops = rooms.length / pag_num - 1 | ||||
| @@ -142,7 +142,7 @@ class User < ApplicationRecord | ||||
|     full_res = bbb.get_recordings(meetingID: last_pag_room.pluck(:bbb_id)) | ||||
|     res[:recordings].push(*full_res[:recordings]) | ||||
|  | ||||
|     format_recordings(res) | ||||
|     format_recordings(res, search_params, ret_search_params) | ||||
|   end | ||||
|  | ||||
|   # Activates an account and initialize a users main room | ||||
| @@ -200,9 +200,8 @@ class User < ApplicationRecord | ||||
|  | ||||
|   def greenlight_account? | ||||
|     return true unless provider # For testing cases when provider is set to null | ||||
|     return provider == "greenlight" unless Rails.configuration.loadbalanced_configuration | ||||
|     # No need to retrive the provider info if the provider is whitelisted | ||||
|     return true if launcher_allow_user_signup_whitelisted?(provider) | ||||
|     return true if provider == "greenlight" | ||||
|     return false unless Rails.configuration.loadbalanced_configuration | ||||
|     # Proceed with fetching the provider info | ||||
|     provider_info = retrieve_provider_info(provider, 'api2', 'getUserGreenlightCredentials') | ||||
|     provider_info['provider'] == 'greenlight' | ||||
|   | ||||
| @@ -16,6 +16,9 @@ | ||||
| <%= render 'shared/room_event' do %> | ||||
|   <%= form_for room_path(@room), method: :post do |f| %> | ||||
|     <div class="input-group join-input"> | ||||
|       <%= f.hidden_field(:search, :value => params[:search])%> | ||||
|       <%= f.hidden_field(:column, :value => params[:column])%> | ||||
|       <%= f.hidden_field(:direction, :value => params[:direction])%> | ||||
|       <%= f.text_field :join_name, | ||||
|           required: true, | ||||
|           class: "form-control join-form", | ||||
|   | ||||
| @@ -15,8 +15,8 @@ | ||||
|  | ||||
| <div class="background pb-1"> | ||||
|   <div class="container"> | ||||
|     <div class="row pt-7"> | ||||
|       <div class="col-lg-9 col-sm-12"> | ||||
|     <div class="row pt-9"> | ||||
|       <div class="col-lg-8 col-sm-12"> | ||||
|         <div id="room-title" class="display-3 form-inline <%= 'edit_hover_class' if current_user.main_room != @room %>"> | ||||
|           <h1 contenteditable=false id="user-text" class="display-3 text-left mb-3 font-weight-400"><%= @room.name %></h1> | ||||
|           <% if current_user.main_room == @room %> | ||||
| @@ -27,20 +27,36 @@ | ||||
|         </div> | ||||
|         <h4 class="text-left mb-6"><%= @room.sessions %> <%= t("room.sessions") %> | <%= @recordings.length %> <%= t("room.recordings") %></h4> | ||||
|         <label class="form-label"><%= t("room.invite_participants") %></label> | ||||
|         <form class="form-inline"> | ||||
|         <div class="row"> | ||||
|           <div class="col-lg-5 col-md-12 mt-2 pr-0"> | ||||
|             <div class="input-icon invite-link-input"> | ||||
|               <span class="input-icon-addon"> | ||||
|                 <i class="fas fa-link"></i> | ||||
|               </span> | ||||
|               <input id="invite-url" type="text" class="form-control w-100" value="<%= request.base_url + @room.invite_path %>" readonly=""> | ||||
|             </div> | ||||
|           <div id="copy" class="btn btn-primary mx-2"> | ||||
|           </div> | ||||
|           <div class="col-lg-7 col-md-12 pr-0"> | ||||
|             <div class="row"> | ||||
|               <div class="col-sm-6"> | ||||
|                 <a href="#" id="copy" class="btn btn-primary btn-block mt-2"> | ||||
|                   <i class="fas fa-copy"></i> | ||||
|                   <%= t("copy") %> | ||||
|                 </a> | ||||
|               </div> | ||||
|         </form> | ||||
|               <div class="col-sm-6 pl-0"> | ||||
|                 <% if Rails.configuration.enable_google_calendar_button %> | ||||
|                   <a href="<%= google_calendar_path %>" target="__blank" id="schedule" class="btn btn-primary btn-block mt-2"> | ||||
|                     <i class="fas fa-calendar-plus"></i> | ||||
|                     <%= t("add_to_google_calendar") %> | ||||
|                   </a> | ||||
|                 <% end %> | ||||
|               </div> | ||||
|       <div class="col-lg-3 col-sm-12 force-bottom mt-5"> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="offset-lg-1 col-lg-3 col-sm-12 force-bottom mt-5 pr-0"> | ||||
|         <% if @is_running %> | ||||
|           <%= button_to t("room.join"), room_path(@room), class: "btn btn-primary btn-block px-7 start-button float-right" %> | ||||
|         <% else %> | ||||
| @@ -70,6 +86,6 @@ | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <%= render "shared/sessions", recordings: @recordings, only_public: false, user_recordings: false, title: t("room.recordings")%> | ||||
| <%= render "shared/sessions", recordings: @recordings, pagy: @pagy, only_public: false, user_recordings: false, title: t("room.recordings")%> | ||||
|  | ||||
| <%= render "shared/modals/create_room_modal" %> | ||||
|   | ||||
| @@ -40,4 +40,4 @@ | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <%= render "shared/sessions", recordings: @room.public_recordings, only_public: true, user_recordings: false, title: t("room.recordings") %> | ||||
| <%= render "shared/sessions", recordings: @public_recordings, pagy: @pagy, only_public: true, user_recordings: false, title: t("room.recordings") %> | ||||
|   | ||||
| @@ -21,21 +21,54 @@ | ||||
|       <div class="col-12"> | ||||
|         <div class="card"> | ||||
|           <div class="table-responsive"> | ||||
|             <table class="table table-hover table-outline table-vcenter text-nowrap card-table"> | ||||
|             <table id="recordings-table" class="table table-hover table-outline table-vcenter text-nowrap card-table"> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th data-header="<%= t("recording.table.name") %>" data-order="none"><%= t("recording.table.name") %></th> | ||||
|                   <% if recording_thumbnails? %> | ||||
|                     <th><%= t("recording.table.thumbnails") %></th> | ||||
|                   <th data-header="name" data-order="<%= @order_column == "name" ? @order_direction : "none" %>"> | ||||
|                     <%= t("recording.table.name") %> | ||||
|                     <% if @order_column == "name" && @order_direction == "desc" %> | ||||
|                       ↓ | ||||
|                     <% elsif @order_column == "name" && @order_direction == "asc" %> | ||||
|                       ↑ | ||||
|                     <% end %> | ||||
|                   <th class="text-left" data-header="<%= t("recording.table.length") %>" data-order="none"> | ||||
|                   </th> | ||||
|                   <% if recording_thumbnails? %> | ||||
|                     <th> | ||||
|                       <%= t("recording.table.thumbnails") %> | ||||
|                     </th> | ||||
|                   <% end %> | ||||
|                   <th class="text-left" data-header="length" data-order="<%= @order_column == "length" ? @order_direction : "none" %>"> | ||||
|                     <%= t("recording.table.length") %> | ||||
|                     <% if @order_column == "length" && @order_direction == "desc" %> | ||||
|                       ↓ | ||||
|                     <% elsif @order_column == "length" && @order_direction == "asc" %> | ||||
|                       ↑ | ||||
|                     <% end %> | ||||
|                   </th> | ||||
|                   <th class="text-left" data-header="<%= t("recording.table.users") %>" data-order="none"> | ||||
|                   <th class="text-left" data-header="users" data-order="<%= @order_column == "users" ? @order_direction : "none" %>"> | ||||
|                     <%= t("recording.table.users") %> | ||||
|                     <% if @order_column == "users" && @order_direction == "desc" %> | ||||
|                       ↓ | ||||
|                     <% elsif @order_column == "users" && @order_direction == "asc" %> | ||||
|                       ↑ | ||||
|                     <% end %> | ||||
|                   </th> | ||||
|                   <th class="text-left" data-header="visibility" data-order="<%= @order_column == "visibility" ? @order_direction : "none" %>"> | ||||
|                     <%= t("recording.table.visibility") %> | ||||
|                     <% if @order_column == "visibility" && @order_direction == "desc" %> | ||||
|                       ↓ | ||||
|                     <% elsif @order_column == "visibility" && @order_direction == "asc" %> | ||||
|                       ↑ | ||||
|                     <% end %> | ||||
|                   </th> | ||||
|                   <th data-header="formats" data-order="<%= @order_column == "formats" ? @order_direction : "none" %>"> | ||||
|                     <%= t("recording.table.formats") %> | ||||
|                     <% if @order_column == "formats" && @order_direction == "desc" %> | ||||
|                       ↓ | ||||
|                     <% elsif @order_column == "formats" && @order_direction == "asc" %> | ||||
|                       ↑ | ||||
|                     <% end %> | ||||
|                   </th> | ||||
|                   <th class="text-left"><%= t("recording.table.visibility") %></th> | ||||
|                   <th><%= t("recording.table.formats") %></th> | ||||
|                   <% unless only_public %> | ||||
|                     <th class="text-center"><i class="icon-settings"></i></th> | ||||
|                   <% end %> | ||||
| @@ -68,6 +101,11 @@ | ||||
|                 <% end %> | ||||
|               </tbody> | ||||
|             </table> | ||||
|             <% if !recordings.empty?%> | ||||
|               <div class="float-right mr-4 mt-4"> | ||||
|                 <%== pagy_bootstrap_nav(pagy) %> | ||||
|               </div> | ||||
|             <% end %> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|         <div class="input-group"> | ||||
|           <input id="branding-url" type="text" class="form-control" value="<%= logo_image %>"> | ||||
|           <span class="input-group-append"> | ||||
|             <button onclick="changeBrandingImage('<%= admin_branding_path %>')" class="btn btn-primary" type="button"><%= t("administrator.site_settings.branding.change") %></button> | ||||
|             <button id="branding-image" onclick="changeBrandingImage('<%= admin_branding_path %>')" class="btn btn-primary" type="button"><%= t("administrator.site_settings.branding.change") %></button> | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
| @@ -33,12 +33,12 @@ | ||||
|         <div class="row gutters-xs"> | ||||
|           <div class="col-auto"> | ||||
|             <input id="coloring-path" value="<%= admin_coloring_path %>" hidden> | ||||
|             <label class="colorinput"> | ||||
|               <input name="color" type="color" value="<%= user_color %>" class="colorinput-input" /> | ||||
|             <input id="user-colour" value="<%= user_color %>" hidden/> | ||||
|             <div class="colorinput"> | ||||
|               <span class="colorinput-color" style="background: <%= user_color %>;"> | ||||
|                 <i class="p-1 fas fa-paint-brush"></i> | ||||
|               </span> | ||||
|             </label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -39,7 +39,7 @@ | ||||
|       <% end %> | ||||
|     </td> | ||||
|   <% end %> | ||||
|   <td id="recording-length" class="text-left" data-full-length="<%=recording[:playbacks].first[:length]%>"> | ||||
|   <td id="recording-length" class="text-left" data-full-length="<%= recording[:playbacks].empty? ? 0 : recording[:playbacks].first[:length]%>"> | ||||
|     <%= recording_length(recording[:playbacks]) %> | ||||
|   </td> | ||||
|   <td id="recording-users" class="text-left"> | ||||
| @@ -63,7 +63,7 @@ | ||||
|     </div> | ||||
|   </td> | ||||
|   <td> | ||||
|     <% sorted_formats =  recording[:playbacks].sort_by! {|p| p[:type]} %> | ||||
|     <% sorted_formats = recording[:playbacks].sort_by! { |p| p[:type] } %> | ||||
|     <% sorted_formats.each do |p| %> | ||||
|       <%= link_to t("recording.format.#{p[:type]}"), p[:url], class: "btn btn-sm btn-primary", target: "_blank" %> | ||||
|     <% end %> | ||||
|   | ||||
| @@ -18,14 +18,24 @@ | ||||
|     <p class="subtitle"><%= subtitle %></p> | ||||
|   </div> | ||||
|   <% if search %> | ||||
|     <div id="search_bar" class="col-4"> | ||||
|       <div class="input-icon"> | ||||
|         <input type="text" class="form-control btn-pill" placeholder="Search..."> | ||||
|         <span class="input-icon-addon"> | ||||
|         <i class="fas fa-search"></i> | ||||
|     <div class="col-4"> | ||||
|       <div id="search-bar"> | ||||
|         <div class="input-group"> | ||||
|           <input id="search-input" type="text" class="form-control" placeholder="<%= t("settings.search") %>..." value="<%= @search %>"> | ||||
|           <% unless @search.blank? %> | ||||
|             <span id="clear-search" class="text-primary" onclick="clearSearch()"> | ||||
|               <i class="fas fa-times"></i> | ||||
|             </span> | ||||
|           <% end %> | ||||
|           <span class="input-group-append"> | ||||
|             <button class="btn btn-primary" type="button" onclick="searchPage()"> | ||||
|               <i class="fas fa-search"></i> | ||||
|             </button> | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   <% end %> | ||||
| </div> | ||||
|  | ||||
| <hr class="mt-0"> | ||||
| @@ -17,38 +17,9 @@ | ||||
|   <div class="card-body p-6"> | ||||
|     <div class="card-title text-primary"> | ||||
|       <div class="form-group"> | ||||
|         <div class="row"> | ||||
|           <% if setting_id == "users" %> | ||||
|             <div class="col-7 mt-2"> | ||||
|               <h4><%= setting_title %></h4> | ||||
|             </div> | ||||
|  | ||||
|             <div class="col-5 float-right"> | ||||
|               <div id="search-bar"> | ||||
|                 <div class="input-group"> | ||||
|                   <input id="search-input" type="text" class="form-control" placeholder="<%= t("settings.search") %>..." value="<%= @search %>"> | ||||
|                   <% unless @search.blank? %> | ||||
|                     <span id="clear-search" class="text-primary" onclick="clearSearch()"> | ||||
|                       <i class="fas fa-times"></i> | ||||
|                     </span> | ||||
|                   <% end %> | ||||
|                   <span class="input-group-append"> | ||||
|                     <button class="btn btn-primary" type="button" onclick="searchPage()"> | ||||
|                       <i class="fas fa-search"></i> | ||||
|                     </button> | ||||
|                   </span> | ||||
|         <%= render "shared/components/subtitle", subtitle: setting_title, search: setting_id == "users" %> | ||||
|       </div> | ||||
|     </div> | ||||
|             </div> | ||||
|           <% else %> | ||||
|             <div class="col-12 mt-2"> | ||||
|               <h4 class="text-primary"><%= setting_title %></h4> | ||||
|             </div> | ||||
|           <% end %> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <hr> | ||||
|  | ||||
|     <% unless (defined?(admin_view)).nil? %> | ||||
|       <%= render "shared/admin_settings/#{setting_id}" %> | ||||
|   | ||||
| @@ -18,4 +18,4 @@ | ||||
| # without losing all css | ||||
| %> | ||||
|  | ||||
| <%= render "shared/sessions", recordings: @recordings, only_public: false, user_recordings: true, title: t("recording.all_recordings") %> | ||||
| <%= render "shared/sessions", recordings: @recordings,  pagy: @pagy, only_public: false, user_recordings: true, title: t("recording.all_recordings") %> | ||||
|   | ||||
| @@ -48,7 +48,7 @@ module Greenlight | ||||
|     config.gl_callback_url = ENV["GL_CALLBACK_URL"] | ||||
|  | ||||
|     # Default credentials (test-install.blindsidenetworks.com/bigbluebutton). | ||||
|     config.bigbluebutton_endpoint_default = "http://test-install.blindsidenetworks.com/bigbluebutton/" | ||||
|     config.bigbluebutton_endpoint_default = "http://test-install.blindsidenetworks.com/bigbluebutton/api/" | ||||
|     config.bigbluebutton_secret_default = "8cd8ef52e8e101574e400365b55e11a6" | ||||
|  | ||||
|     # Use standalone BigBlueButton server. | ||||
| @@ -66,7 +66,6 @@ module Greenlight | ||||
|       config.loadbalancer_endpoint = ENV["LOADBALANCER_ENDPOINT"] | ||||
|       config.loadbalancer_secret = ENV["LOADBALANCER_SECRET"] | ||||
|       config.launcher_secret = ENV["LAUNCHER_SECRET"] | ||||
|       config.launcher_allow_user_signup = ENV["LAUNCHER_ALLOW_GREENLIGHT_ACCOUNTS"] | ||||
|  | ||||
|       # Fix endpoint format if required. | ||||
|       config.loadbalancer_endpoint += "/" unless config.bigbluebutton_endpoint.ends_with?("/") | ||||
| @@ -111,5 +110,8 @@ module Greenlight | ||||
|  | ||||
|     # Whether the user has defined the variables required for recaptcha | ||||
|     config.recaptcha_enabled = ENV['RECAPTCHA_SITE_KEY'].present? && ENV['RECAPTCHA_SECRET_KEY'].present? | ||||
|  | ||||
|     # Show/hide "Add to Google Calendar" button in the room page | ||||
|     config.enable_google_calendar_button = (ENV['ENABLE_GOOGLE_CALENDAR_BUTTON'] == "true") | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|  | ||||
| # Array extra: Paginate arrays efficiently, avoiding expensive array-wrapping and without overriding | ||||
| # See https://ddnexus.github.io/pagy/extras/array | ||||
| # require 'pagy/extras/array' | ||||
| require 'pagy/extras/array' | ||||
|  | ||||
| # Countless extra: Paginate without any count, saving one query per rendering | ||||
| # See https://ddnexus.github.io/pagy/extras/countless | ||||
|   | ||||
| @@ -62,12 +62,14 @@ en: | ||||
|         uid: User ID | ||||
|         username: Username | ||||
|       title: Manage Users | ||||
|   add_to_google_calendar: "Add to Google Calendar" | ||||
|   bigbluebutton: BigBlueButton | ||||
|   bigbluebutton_exception: Oops, there was an error when starting the meeting! | ||||
|   cancel: Cancel | ||||
|   cookies: | ||||
|     cookie_info: Cookies help us deliver our services. By using our services, you agree to our use of cookies. | ||||
|     cookie_button: I Agree | ||||
|   copied: Copied | ||||
|   copy: Copy | ||||
|   default_admin: You are still using the default password for this account. Please click <a href="%{edit_link}">here</a> to change it | ||||
|   delete: Delete | ||||
|   | ||||
| @@ -73,10 +73,4 @@ module BbbApi | ||||
|   def remove_slash(s) | ||||
|     s.nil? ? nil : s.chomp("/") | ||||
|   end | ||||
|  | ||||
|   def launcher_allow_user_signup_whitelisted?(provider) | ||||
|     return false unless Rails.configuration.launcher_allow_user_signup | ||||
|     whitelist = Rails.configuration.launcher_allow_user_signup.split(',') | ||||
|     whitelist.include?(provider) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace :conf do | ||||
|     # Tests the checksum on the getMeetings api call | ||||
|     print "Checking Secret" | ||||
|     checksum = Digest::SHA1.hexdigest("getMeetings#{ENV['BIGBLUEBUTTON_SECRET']}") | ||||
|     test_request("#{ENV['BIGBLUEBUTTON_ENDPOINT']}api/getMeetings?checksum=#{checksum}") | ||||
|     test_request("#{ENV['BIGBLUEBUTTON_ENDPOINT']}getMeetings?checksum=#{checksum}") | ||||
|     passed | ||||
|  | ||||
|     if ENV['ALLOW_MAIL_NOTIFICATIONS'] == 'true' | ||||
|   | ||||
| @@ -139,6 +139,10 @@ PAGINATION_NUMBER=25 | ||||
| # Default is set to 10 rows | ||||
| NUMBER_OF_ROWS=10 | ||||
|  | ||||
| # Specify if you want to display the Google Calendar button | ||||
| #   ENABLE_GOOGLE_CALENDAR_BUTTON=true|false | ||||
| ENABLE_GOOGLE_CALENDAR_BUTTON= | ||||
|  | ||||
| # Comment this out to send logs to STDOUT in production instead of log/production.log . | ||||
| # | ||||
| # RAILS_LOG_TO_STDOUT=true | ||||
|   | ||||
| @@ -65,20 +65,9 @@ describe ApplicationHelper do | ||||
|       expect(helper.allow_greenlight_accounts?).to eql(false) | ||||
|     end | ||||
|  | ||||
|     it "allows if user_domain is white listed" do | ||||
|       allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) | ||||
|       allow(Rails.configuration).to receive(:allow_user_signup).and_return(true) | ||||
|       allow(helper).to receive(:launcher_allow_user_signup_whitelisted?).and_return(true) | ||||
|  | ||||
|       @user_domain = "provider1" | ||||
|  | ||||
|       expect(helper.allow_greenlight_accounts?).to eql(true) | ||||
|     end | ||||
|  | ||||
|     it "allows if user provider is set to greenlight" do | ||||
|       allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) | ||||
|       allow(Rails.configuration).to receive(:allow_user_signup).and_return(true) | ||||
|       allow(helper).to receive(:launcher_allow_user_signup_whitelisted?).and_return(false) | ||||
|       allow(helper).to receive(:retrieve_provider_info).and_return("provider" => "greenlight") | ||||
|  | ||||
|       @user_domain = "provider1" | ||||
| @@ -89,7 +78,6 @@ describe ApplicationHelper do | ||||
|     it "doesnt allow if user provider is not set to greenlight" do | ||||
|       allow(Rails.configuration).to receive(:loadbalanced_configuration).and_return(true) | ||||
|       allow(Rails.configuration).to receive(:allow_user_signup).and_return(true) | ||||
|       allow(helper).to receive(:launcher_allow_user_signup_whitelisted?).and_return(false) | ||||
|       allow(helper).to receive(:retrieve_provider_info).and_return("provider" => "google") | ||||
|  | ||||
|       @user_domain = "provider1" | ||||
|   | ||||
| @@ -139,18 +139,414 @@ describe Room, type: :model do | ||||
|           { | ||||
|             name: "Example", | ||||
|             playback: { | ||||
|               format: "presentation", | ||||
|             }, | ||||
|           }, | ||||
|         ], | ||||
|               format: | ||||
|               { | ||||
|                 type: "presentation" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|       ) | ||||
|  | ||||
|       expect(@room.recordings).to contain_exactly( | ||||
|         name: "Example", | ||||
|         playbacks: %w(presentation), | ||||
|         playbacks: | ||||
|         [ | ||||
|           { | ||||
|             type: "presentation" | ||||
|           } | ||||
|         ] | ||||
|       ) | ||||
|     end | ||||
|  | ||||
|     context '#filtering' do | ||||
|       before do | ||||
|         allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( | ||||
|           recordings: [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playback: { | ||||
|                 format: | ||||
|                 { | ||||
|                   type: "presentation" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "5", | ||||
|               playback: { | ||||
|                 format: | ||||
|                 { | ||||
|                   type: "other" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 "gl-listed": "false", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "test", | ||||
|               participants: "1", | ||||
|               playback: { | ||||
|                 format: | ||||
|                 { | ||||
|                   type: "presentation" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "Exam", | ||||
|               participants: "1", | ||||
|               playback: { | ||||
|                 format: | ||||
|                 { | ||||
|                   type: "other" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 "gl-listed": "false", | ||||
|                 name: "z", | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should filter recordings on name" do | ||||
|         expect(@room.recordings(search: "Exam")).to contain_exactly( | ||||
|           { | ||||
|             name: "aExamaaa", | ||||
|             participants: "5", | ||||
|             playbacks: | ||||
|               [ | ||||
|                 { | ||||
|                   type: "other" | ||||
|                 } | ||||
|               ], | ||||
|             metadata: { | ||||
|               "gl-listed": "false", | ||||
|             } | ||||
|           }, | ||||
|             name: "Example", | ||||
|             participants: "3", | ||||
|             playbacks: | ||||
|               [ | ||||
|                 { | ||||
|                   type: "presentation" | ||||
|                 } | ||||
|               ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should filter recordings on participants" do | ||||
|         expect(@room.recordings(search: "5")).to contain_exactly( | ||||
|           name: "aExamaaa", | ||||
|           participants: "5", | ||||
|           playbacks: | ||||
|             [ | ||||
|               { | ||||
|                 type: "other" | ||||
|               } | ||||
|             ], | ||||
|           metadata: { | ||||
|             "gl-listed": "false", | ||||
|           } | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should filter recordings on format" do | ||||
|         expect(@room.recordings(search: "presentation")).to contain_exactly( | ||||
|           { | ||||
|             name: "test", | ||||
|             participants: "1", | ||||
|             playbacks: | ||||
|                 [ | ||||
|                   { | ||||
|                     type: "presentation" | ||||
|                   } | ||||
|                 ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|           }, | ||||
|             name: "Example", | ||||
|             participants: "3", | ||||
|             playbacks: | ||||
|                 [ | ||||
|                   { | ||||
|                     type: "presentation" | ||||
|                   } | ||||
|                 ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should filter recordings on visibility" do | ||||
|         expect(@room.recordings(search: "public")).to contain_exactly( | ||||
|           { | ||||
|             name: "test", | ||||
|             participants: "1", | ||||
|             playbacks: | ||||
|                 [ | ||||
|                   { | ||||
|                     type: "presentation" | ||||
|                   } | ||||
|                 ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             }, | ||||
|           }, | ||||
|             name: "Example", | ||||
|             participants: "3", | ||||
|             playbacks: | ||||
|                 [ | ||||
|                   { | ||||
|                     type: "presentation" | ||||
|                   } | ||||
|                 ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should filter recordings on metadata name by default" do | ||||
|         expect(@room.recordings(search: "z")).to contain_exactly( | ||||
|           name: "Exam", | ||||
|           participants: "1", | ||||
|           playbacks: | ||||
|               [ | ||||
|                 { | ||||
|                   type: "other" | ||||
|                 } | ||||
|               ], | ||||
|           metadata: { | ||||
|             "gl-listed": "false", | ||||
|             name: "z", | ||||
|           } | ||||
|         ) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context '#sorting' do | ||||
|       before do | ||||
|         allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( | ||||
|           recordings: [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playback: { | ||||
|                 format: { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playback: { | ||||
|                 format: { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               }, | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should sort recordings on name" do | ||||
|         expect(@room.recordings(column: "name", direction: "asc")).to eq( | ||||
|           [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should sort recordings on participants" do | ||||
|         expect(@room.recordings(column: "users", direction: "desc")).to eq( | ||||
|           [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should sort recordings on visibility" do | ||||
|         expect(@room.recordings(column: "visibility", direction: "desc")).to eq( | ||||
|           [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should sort recordings on length" do | ||||
|         expect(@room.recordings(column: "length", direction: "asc")).to eq( | ||||
|           [ | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|  | ||||
|       it "should sort recordings on format" do | ||||
|         expect(@room.recordings(column: "formats", direction: "desc")).to eq( | ||||
|           [ | ||||
|             { | ||||
|               name: "Example", | ||||
|               participants: "3", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "presentation", | ||||
|                   length: "4" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 "gl-listed": "true", | ||||
|               } | ||||
|             }, | ||||
|             { | ||||
|               name: "aExamaaa", | ||||
|               participants: "1", | ||||
|               playbacks: [ | ||||
|                 { | ||||
|                   type: "other", | ||||
|                   length: "3" | ||||
|                 } | ||||
|               ], | ||||
|               metadata: { | ||||
|                 name: "Z", | ||||
|                 "gl-listed": "false" | ||||
|               } | ||||
|             } | ||||
|           ] | ||||
|         ) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     it "deletes the recording" do | ||||
|       allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:delete_recordings).and_return( | ||||
|         returncode: true, deleted: true | ||||
|   | ||||
| @@ -173,4 +173,97 @@ describe User, type: :model do | ||||
|         .to raise_exception(ActiveRecord::RecordInvalid, "Validation failed: Email can't be blank") | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   context '#recordings' do | ||||
|     it "gets all filtered and sorted recordings for the user" do | ||||
|       allow_any_instance_of(BigBlueButton::BigBlueButtonApi).to receive(:get_recordings).and_return( | ||||
|         recordings: [ | ||||
|           { | ||||
|             name: "Example", | ||||
|             participants: "3", | ||||
|             playback: { | ||||
|               format: | ||||
|               { | ||||
|                 type: "presentation" | ||||
|               } | ||||
|             }, | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             name: "aExamaaa", | ||||
|             participants: "5", | ||||
|             playback: { | ||||
|               format: | ||||
|               { | ||||
|                 type: "other" | ||||
|               } | ||||
|             }, | ||||
|             metadata: { | ||||
|               "gl-listed": "false", | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             name: "test", | ||||
|             participants: "1", | ||||
|             playback: { | ||||
|               format: | ||||
|               { | ||||
|                 type: "presentation" | ||||
|               } | ||||
|             }, | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             name: "Exam", | ||||
|             participants: "1", | ||||
|             playback: { | ||||
|               format: | ||||
|               { | ||||
|                 type: "other" | ||||
|               } | ||||
|             }, | ||||
|             metadata: { | ||||
|               "gl-listed": "false", | ||||
|               name: "z", | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|       ) | ||||
|  | ||||
|       expect(@user.all_recordings(search: "Exam", column: "name", direction: "desc")).to eq( | ||||
|         [ | ||||
|           { | ||||
|             name: "Example", | ||||
|             participants: "3", | ||||
|             playbacks: | ||||
|               [ | ||||
|                 { | ||||
|                   type: "presentation" | ||||
|                 } | ||||
|               ], | ||||
|             metadata: { | ||||
|               "gl-listed": "true", | ||||
|             } | ||||
|           }, | ||||
|           { | ||||
|             name: "aExamaaa", | ||||
|             participants: "5", | ||||
|             playbacks: | ||||
|               [ | ||||
|                 { | ||||
|                   type: "other" | ||||
|                 } | ||||
|               ], | ||||
|             metadata: { | ||||
|               "gl-listed": "false", | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|       ) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/blank.gif
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 49 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_background.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_hex.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 532 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_hsb_b.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 970 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_hsb_h.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1012 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_hsb_s.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_indic.gif
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 86 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_overlay.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_rgb_b.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 970 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_rgb_g.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_rgb_r.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_select.gif
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 78 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/colorpicker_submit.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 984 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_background.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_hex.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 562 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_hsb_b.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_hsb_h.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 970 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_hsb_s.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_indic.gif
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 86 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_rgb_b.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1008 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_rgb_g.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_rgb_r.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1018 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/custom_submit.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 997 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/select.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 506 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/select2.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 518 B | 
							
								
								
									
										
											BIN
										
									
								
								vendor/assets/images/colourPicker/slider.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 315 B | 
							
								
								
									
										484
									
								
								vendor/assets/javascripts/colorpicker.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,484 @@ | ||||
| /** | ||||
|  * | ||||
|  * Color picker | ||||
|  * Author: Stefan Petre www.eyecon.ro | ||||
|  *  | ||||
|  * Dual licensed under the MIT and GPL licenses | ||||
|  *  | ||||
|  */ | ||||
| (function ($) { | ||||
| 	var ColorPicker = function () { | ||||
| 		var | ||||
| 			ids = {}, | ||||
| 			inAction, | ||||
| 			charMin = 65, | ||||
| 			visible, | ||||
| 			tpl = '<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div></div>', | ||||
| 			defaults = { | ||||
| 				eventName: 'click', | ||||
| 				onShow: function () {}, | ||||
| 				onBeforeShow: function(){}, | ||||
| 				onHide: function () {}, | ||||
| 				onChange: function () {}, | ||||
| 				onSubmit: function () {}, | ||||
| 				color: 'ff0000', | ||||
| 				livePreview: true, | ||||
| 				flat: false | ||||
| 			}, | ||||
| 			fillRGBFields = function  (hsb, cal) { | ||||
| 				var rgb = HSBToRGB(hsb); | ||||
| 				$(cal).data('colorpicker').fields | ||||
| 					.eq(1).val(rgb.r).end() | ||||
| 					.eq(2).val(rgb.g).end() | ||||
| 					.eq(3).val(rgb.b).end(); | ||||
| 			}, | ||||
| 			fillHSBFields = function  (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').fields | ||||
| 					.eq(4).val(hsb.h).end() | ||||
| 					.eq(5).val(hsb.s).end() | ||||
| 					.eq(6).val(hsb.b).end(); | ||||
| 			}, | ||||
| 			fillHexFields = function (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').fields | ||||
| 					.eq(0).val(HSBToHex(hsb)).end(); | ||||
| 			}, | ||||
| 			setSelector = function (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100})); | ||||
| 				$(cal).data('colorpicker').selectorIndic.css({ | ||||
| 					left: parseInt(150 * hsb.s/100, 10), | ||||
| 					top: parseInt(150 * (100-hsb.b)/100, 10) | ||||
| 				}); | ||||
| 			}, | ||||
| 			setHue = function (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10)); | ||||
| 			}, | ||||
| 			setCurrentColor = function (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb)); | ||||
| 			}, | ||||
| 			setNewColor = function (hsb, cal) { | ||||
| 				$(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb)); | ||||
| 			}, | ||||
| 			keyDown = function (ev) { | ||||
| 				var pressedKey = ev.charCode || ev.keyCode || -1; | ||||
| 				if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) { | ||||
| 					return false; | ||||
| 				} | ||||
| 				var cal = $(this).parent().parent(); | ||||
| 				if (cal.data('colorpicker').livePreview === true) { | ||||
| 					change.apply(this); | ||||
| 				} | ||||
| 			}, | ||||
| 			change = function (ev) { | ||||
| 				var cal = $(this).parent().parent(), col; | ||||
| 				if (this.parentNode.className.indexOf('_hex') > 0) { | ||||
| 					cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value)); | ||||
| 				} else if (this.parentNode.className.indexOf('_hsb') > 0) { | ||||
| 					cal.data('colorpicker').color = col = fixHSB({ | ||||
| 						h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10), | ||||
| 						s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10), | ||||
| 						b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10) | ||||
| 					}); | ||||
| 				} else { | ||||
| 					cal.data('colorpicker').color = col = RGBToHSB(fixRGB({ | ||||
| 						r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10), | ||||
| 						g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10), | ||||
| 						b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10) | ||||
| 					})); | ||||
| 				} | ||||
| 				if (ev) { | ||||
| 					fillRGBFields(col, cal.get(0)); | ||||
| 					fillHexFields(col, cal.get(0)); | ||||
| 					fillHSBFields(col, cal.get(0)); | ||||
| 				} | ||||
| 				setSelector(col, cal.get(0)); | ||||
| 				setHue(col, cal.get(0)); | ||||
| 				setNewColor(col, cal.get(0)); | ||||
| 				cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]); | ||||
| 			}, | ||||
| 			blur = function (ev) { | ||||
| 				var cal = $(this).parent().parent(); | ||||
| 				cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus'); | ||||
| 			}, | ||||
| 			focus = function () { | ||||
| 				charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65; | ||||
| 				$(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus'); | ||||
| 				$(this).parent().addClass('colorpicker_focus'); | ||||
| 			}, | ||||
| 			downIncrement = function (ev) { | ||||
| 				var field = $(this).parent().find('input').focus(); | ||||
| 				var current = { | ||||
| 					el: $(this).parent().addClass('colorpicker_slider'), | ||||
| 					max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), | ||||
| 					y: ev.pageY, | ||||
| 					field: field, | ||||
| 					val: parseInt(field.val(), 10), | ||||
| 					preview: $(this).parent().parent().data('colorpicker').livePreview					 | ||||
| 				}; | ||||
| 				$(document).bind('mouseup', current, upIncrement); | ||||
| 				$(document).bind('mousemove', current, moveIncrement); | ||||
| 			}, | ||||
| 			moveIncrement = function (ev) { | ||||
| 				ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10)))); | ||||
| 				if (ev.data.preview) { | ||||
| 					change.apply(ev.data.field.get(0), [true]); | ||||
| 				} | ||||
| 				return false; | ||||
| 			}, | ||||
| 			upIncrement = function (ev) { | ||||
| 				change.apply(ev.data.field.get(0), [true]); | ||||
| 				ev.data.el.removeClass('colorpicker_slider').find('input').focus(); | ||||
| 				$(document).unbind('mouseup', upIncrement); | ||||
| 				$(document).unbind('mousemove', moveIncrement); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			downHue = function (ev) { | ||||
| 				var current = { | ||||
| 					cal: $(this).parent(), | ||||
| 					y: $(this).offset().top | ||||
| 				}; | ||||
| 				current.preview = current.cal.data('colorpicker').livePreview; | ||||
| 				$(document).bind('mouseup', current, upHue); | ||||
| 				$(document).bind('mousemove', current, moveHue); | ||||
| 			}, | ||||
| 			moveHue = function (ev) { | ||||
| 				change.apply( | ||||
| 					ev.data.cal.data('colorpicker') | ||||
| 						.fields | ||||
| 						.eq(4) | ||||
| 						.val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10)) | ||||
| 						.get(0), | ||||
| 					[ev.data.preview] | ||||
| 				); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			upHue = function (ev) { | ||||
| 				fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); | ||||
| 				fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); | ||||
| 				$(document).unbind('mouseup', upHue); | ||||
| 				$(document).unbind('mousemove', moveHue); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			downSelector = function (ev) { | ||||
| 				var current = { | ||||
| 					cal: $(this).parent(), | ||||
| 					pos: $(this).offset() | ||||
| 				}; | ||||
| 				current.preview = current.cal.data('colorpicker').livePreview; | ||||
| 				$(document).bind('mouseup', current, upSelector); | ||||
| 				$(document).bind('mousemove', current, moveSelector); | ||||
| 			}, | ||||
| 			moveSelector = function (ev) { | ||||
| 				change.apply( | ||||
| 					ev.data.cal.data('colorpicker') | ||||
| 						.fields | ||||
| 						.eq(6) | ||||
| 						.val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10)) | ||||
| 						.end() | ||||
| 						.eq(5) | ||||
| 						.val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10)) | ||||
| 						.get(0), | ||||
| 					[ev.data.preview] | ||||
| 				); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			upSelector = function (ev) { | ||||
| 				fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); | ||||
| 				fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); | ||||
| 				$(document).unbind('mouseup', upSelector); | ||||
| 				$(document).unbind('mousemove', moveSelector); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			enterSubmit = function (ev) { | ||||
| 				$(this).addClass('colorpicker_focus'); | ||||
| 			}, | ||||
| 			leaveSubmit = function (ev) { | ||||
| 				$(this).removeClass('colorpicker_focus'); | ||||
| 			}, | ||||
| 			clickSubmit = function (ev) { | ||||
| 				var cal = $(this).parent(); | ||||
| 				var col = cal.data('colorpicker').color; | ||||
| 				cal.data('colorpicker').origColor = col; | ||||
| 				setCurrentColor(col, cal.get(0)); | ||||
| 				cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el); | ||||
| 			}, | ||||
| 			show = function (ev) { | ||||
| 				var cal = $('#' + $(this).data('colorpickerId')); | ||||
| 				cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]); | ||||
| 				var pos = $(this).offset(); | ||||
| 				var viewPort = getViewport(); | ||||
| 				var top = pos.top + this.offsetHeight; | ||||
| 				var left = pos.left; | ||||
| 				if (top + 176 > viewPort.t + viewPort.h) { | ||||
| 					top -= this.offsetHeight + 176; | ||||
| 				} | ||||
| 				if (left + 356 > viewPort.l + viewPort.w) { | ||||
| 					left -= 356; | ||||
| 				} | ||||
| 				cal.css({left: left + 'px', top: top + 'px'}); | ||||
| 				if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) { | ||||
| 					cal.show(); | ||||
| 				} | ||||
| 				$(document).bind('mousedown', {cal: cal}, hide); | ||||
| 				return false; | ||||
| 			}, | ||||
| 			hide = function (ev) { | ||||
| 				if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) { | ||||
| 					if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) { | ||||
| 						ev.data.cal.hide(); | ||||
| 					} | ||||
| 					$(document).unbind('mousedown', hide); | ||||
| 				} | ||||
| 			}, | ||||
| 			isChildOf = function(parentEl, el, container) { | ||||
| 				if (parentEl == el) { | ||||
| 					return true; | ||||
| 				} | ||||
| 				if (parentEl.contains) { | ||||
| 					return parentEl.contains(el); | ||||
| 				} | ||||
| 				if ( parentEl.compareDocumentPosition ) { | ||||
| 					return !!(parentEl.compareDocumentPosition(el) & 16); | ||||
| 				} | ||||
| 				var prEl = el.parentNode; | ||||
| 				while(prEl && prEl != container) { | ||||
| 					if (prEl == parentEl) | ||||
| 						return true; | ||||
| 					prEl = prEl.parentNode; | ||||
| 				} | ||||
| 				return false; | ||||
| 			}, | ||||
| 			getViewport = function () { | ||||
| 				var m = document.compatMode == 'CSS1Compat'; | ||||
| 				return { | ||||
| 					l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), | ||||
| 					t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop), | ||||
| 					w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth), | ||||
| 					h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight) | ||||
| 				}; | ||||
| 			}, | ||||
| 			fixHSB = function (hsb) { | ||||
| 				return { | ||||
| 					h: Math.min(360, Math.max(0, hsb.h)), | ||||
| 					s: Math.min(100, Math.max(0, hsb.s)), | ||||
| 					b: Math.min(100, Math.max(0, hsb.b)) | ||||
| 				}; | ||||
| 			},  | ||||
| 			fixRGB = function (rgb) { | ||||
| 				return { | ||||
| 					r: Math.min(255, Math.max(0, rgb.r)), | ||||
| 					g: Math.min(255, Math.max(0, rgb.g)), | ||||
| 					b: Math.min(255, Math.max(0, rgb.b)) | ||||
| 				}; | ||||
| 			}, | ||||
| 			fixHex = function (hex) { | ||||
| 				var len = 6 - hex.length; | ||||
| 				if (len > 0) { | ||||
| 					var o = []; | ||||
| 					for (var i=0; i<len; i++) { | ||||
| 						o.push('0'); | ||||
| 					} | ||||
| 					o.push(hex); | ||||
| 					hex = o.join(''); | ||||
| 				} | ||||
| 				return hex; | ||||
| 			},  | ||||
| 			HexToRGB = function (hex) { | ||||
| 				var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); | ||||
| 				return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; | ||||
| 			}, | ||||
| 			HexToHSB = function (hex) { | ||||
| 				return RGBToHSB(HexToRGB(hex)); | ||||
| 			}, | ||||
| 			RGBToHSB = function (rgb) { | ||||
| 				var hsb = { | ||||
| 					h: 0, | ||||
| 					s: 0, | ||||
| 					b: 0 | ||||
| 				}; | ||||
| 				var min = Math.min(rgb.r, rgb.g, rgb.b); | ||||
| 				var max = Math.max(rgb.r, rgb.g, rgb.b); | ||||
| 				var delta = max - min; | ||||
| 				hsb.b = max; | ||||
| 				if (max != 0) { | ||||
| 					 | ||||
| 				} | ||||
| 				hsb.s = max != 0 ? 255 * delta / max : 0; | ||||
| 				if (hsb.s != 0) { | ||||
| 					if (rgb.r == max) { | ||||
| 						hsb.h = (rgb.g - rgb.b) / delta; | ||||
| 					} else if (rgb.g == max) { | ||||
| 						hsb.h = 2 + (rgb.b - rgb.r) / delta; | ||||
| 					} else { | ||||
| 						hsb.h = 4 + (rgb.r - rgb.g) / delta; | ||||
| 					} | ||||
| 				} else { | ||||
| 					hsb.h = -1; | ||||
| 				} | ||||
| 				hsb.h *= 60; | ||||
| 				if (hsb.h < 0) { | ||||
| 					hsb.h += 360; | ||||
| 				} | ||||
| 				hsb.s *= 100/255; | ||||
| 				hsb.b *= 100/255; | ||||
| 				return hsb; | ||||
| 			}, | ||||
| 			HSBToRGB = function (hsb) { | ||||
| 				var rgb = {}; | ||||
| 				var h = Math.round(hsb.h); | ||||
| 				var s = Math.round(hsb.s*255/100); | ||||
| 				var v = Math.round(hsb.b*255/100); | ||||
| 				if(s == 0) { | ||||
| 					rgb.r = rgb.g = rgb.b = v; | ||||
| 				} else { | ||||
| 					var t1 = v; | ||||
| 					var t2 = (255-s)*v/255; | ||||
| 					var t3 = (t1-t2)*(h%60)/60; | ||||
| 					if(h==360) h = 0; | ||||
| 					if(h<60) {rgb.r=t1;	rgb.b=t2; rgb.g=t2+t3} | ||||
| 					else if(h<120) {rgb.g=t1; rgb.b=t2;	rgb.r=t1-t3} | ||||
| 					else if(h<180) {rgb.g=t1; rgb.r=t2;	rgb.b=t2+t3} | ||||
| 					else if(h<240) {rgb.b=t1; rgb.r=t2;	rgb.g=t1-t3} | ||||
| 					else if(h<300) {rgb.b=t1; rgb.g=t2;	rgb.r=t2+t3} | ||||
| 					else if(h<360) {rgb.r=t1; rgb.g=t2;	rgb.b=t1-t3} | ||||
| 					else {rgb.r=0; rgb.g=0;	rgb.b=0} | ||||
| 				} | ||||
| 				return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; | ||||
| 			}, | ||||
| 			RGBToHex = function (rgb) { | ||||
| 				var hex = [ | ||||
| 					rgb.r.toString(16), | ||||
| 					rgb.g.toString(16), | ||||
| 					rgb.b.toString(16) | ||||
| 				]; | ||||
| 				$.each(hex, function (nr, val) { | ||||
| 					if (val.length == 1) { | ||||
| 						hex[nr] = '0' + val; | ||||
| 					} | ||||
| 				}); | ||||
| 				return hex.join(''); | ||||
| 			}, | ||||
| 			HSBToHex = function (hsb) { | ||||
| 				return RGBToHex(HSBToRGB(hsb)); | ||||
| 			}, | ||||
| 			restoreOriginal = function () { | ||||
| 				var cal = $(this).parent(); | ||||
| 				var col = cal.data('colorpicker').origColor; | ||||
| 				cal.data('colorpicker').color = col; | ||||
| 				fillRGBFields(col, cal.get(0)); | ||||
| 				fillHexFields(col, cal.get(0)); | ||||
| 				fillHSBFields(col, cal.get(0)); | ||||
| 				setSelector(col, cal.get(0)); | ||||
| 				setHue(col, cal.get(0)); | ||||
| 				setNewColor(col, cal.get(0)); | ||||
| 			}; | ||||
| 		return { | ||||
| 			init: function (opt) { | ||||
| 				opt = $.extend({}, defaults, opt||{}); | ||||
| 				if (typeof opt.color == 'string') { | ||||
| 					opt.color = HexToHSB(opt.color); | ||||
| 				} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) { | ||||
| 					opt.color = RGBToHSB(opt.color); | ||||
| 				} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) { | ||||
| 					opt.color = fixHSB(opt.color); | ||||
| 				} else { | ||||
| 					return this; | ||||
| 				} | ||||
| 				return this.each(function () { | ||||
| 					if (!$(this).data('colorpickerId')) { | ||||
| 						var options = $.extend({}, opt); | ||||
| 						options.origColor = opt.color; | ||||
| 						var id = 'collorpicker_' + parseInt(Math.random() * 1000); | ||||
| 						$(this).data('colorpickerId', id); | ||||
| 						var cal = $(tpl).attr('id', id); | ||||
| 						if (options.flat) { | ||||
| 							cal.appendTo(this).show(); | ||||
| 						} else { | ||||
| 							cal.appendTo(document.body); | ||||
| 						} | ||||
| 						options.fields = cal | ||||
| 											.find('input') | ||||
| 												.bind('keyup', keyDown) | ||||
| 												.bind('change', change) | ||||
| 												.bind('blur', blur) | ||||
| 												.bind('focus', focus); | ||||
| 						cal | ||||
| 							.find('span').bind('mousedown', downIncrement).end() | ||||
| 							.find('>div.colorpicker_current_color').bind('click', restoreOriginal); | ||||
| 						options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector); | ||||
| 						options.selectorIndic = options.selector.find('div div'); | ||||
| 						options.el = this; | ||||
| 						options.hue = cal.find('div.colorpicker_hue div'); | ||||
| 						cal.find('div.colorpicker_hue').bind('mousedown', downHue); | ||||
| 						options.newColor = cal.find('div.colorpicker_new_color'); | ||||
| 						options.currentColor = cal.find('div.colorpicker_current_color'); | ||||
| 						cal.data('colorpicker', options); | ||||
| 						cal.find('div.colorpicker_submit') | ||||
| 							.bind('mouseenter', enterSubmit) | ||||
| 							.bind('mouseleave', leaveSubmit) | ||||
| 							.bind('click', clickSubmit); | ||||
| 						fillRGBFields(options.color, cal.get(0)); | ||||
| 						fillHSBFields(options.color, cal.get(0)); | ||||
| 						fillHexFields(options.color, cal.get(0)); | ||||
| 						setHue(options.color, cal.get(0)); | ||||
| 						setSelector(options.color, cal.get(0)); | ||||
| 						setCurrentColor(options.color, cal.get(0)); | ||||
| 						setNewColor(options.color, cal.get(0)); | ||||
| 						if (options.flat) { | ||||
| 							cal.css({ | ||||
| 								position: 'relative', | ||||
| 								display: 'block' | ||||
| 							}); | ||||
| 						} else { | ||||
| 							$(this).bind(options.eventName, show); | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			}, | ||||
| 			showPicker: function() { | ||||
| 				return this.each( function () { | ||||
| 					if ($(this).data('colorpickerId')) { | ||||
| 						show.apply(this); | ||||
| 					} | ||||
| 				}); | ||||
| 			}, | ||||
| 			hidePicker: function() { | ||||
| 				return this.each( function () { | ||||
| 					if ($(this).data('colorpickerId')) { | ||||
| 						$('#' + $(this).data('colorpickerId')).hide(); | ||||
| 					} | ||||
| 				}); | ||||
| 			}, | ||||
| 			setColor: function(col) { | ||||
| 				if (typeof col == 'string') { | ||||
| 					col = HexToHSB(col); | ||||
| 				} else if (col.r != undefined && col.g != undefined && col.b != undefined) { | ||||
| 					col = RGBToHSB(col); | ||||
| 				} else if (col.h != undefined && col.s != undefined && col.b != undefined) { | ||||
| 					col = fixHSB(col); | ||||
| 				} else { | ||||
| 					return this; | ||||
| 				} | ||||
| 				return this.each(function(){ | ||||
| 					if ($(this).data('colorpickerId')) { | ||||
| 						var cal = $('#' + $(this).data('colorpickerId')); | ||||
| 						cal.data('colorpicker').color = col; | ||||
| 						cal.data('colorpicker').origColor = col; | ||||
| 						fillRGBFields(col, cal.get(0)); | ||||
| 						fillHSBFields(col, cal.get(0)); | ||||
| 						fillHexFields(col, cal.get(0)); | ||||
| 						setHue(col, cal.get(0)); | ||||
| 						setSelector(col, cal.get(0)); | ||||
| 						setCurrentColor(col, cal.get(0)); | ||||
| 						setNewColor(col, cal.get(0)); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 		}; | ||||
| 	}(); | ||||
| 	$.fn.extend({ | ||||
| 		ColorPicker: ColorPicker.init, | ||||
| 		ColorPickerHide: ColorPicker.hidePicker, | ||||
| 		ColorPickerShow: ColorPicker.showPicker, | ||||
| 		ColorPickerSetColor: ColorPicker.setColor | ||||
| 	}); | ||||
| })(jQuery) | ||||
							
								
								
									
										161
									
								
								vendor/assets/stylesheets/colorpicker.scss
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,161 @@ | ||||
| .colorpicker { | ||||
| 	width: 356px; | ||||
| 	height: 176px; | ||||
| 	overflow: hidden; | ||||
| 	position: absolute; | ||||
| 	background: image-url("colourPicker/colorpicker_background.png"); | ||||
| 	font-family: Arial, Helvetica, sans-serif; | ||||
| 	display: none; | ||||
| } | ||||
| .colorpicker_color { | ||||
| 	width: 150px; | ||||
| 	height: 150px; | ||||
| 	left: 14px; | ||||
| 	top: 13px; | ||||
| 	position: absolute; | ||||
| 	background: #f00; | ||||
| 	overflow: hidden; | ||||
| 	cursor: crosshair; | ||||
| } | ||||
| .colorpicker_color div { | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	width: 150px; | ||||
| 	height: 150px; | ||||
| 	background: image-url("colourPicker/colorpicker_overlay.png"); | ||||
| } | ||||
| .colorpicker_color div div { | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	width: 11px; | ||||
| 	height: 11px; | ||||
| 	overflow: hidden; | ||||
| 	background: image-url("colourPicker/colorpicker_select.gif"); | ||||
| 	margin: -5px 0 0 -5px; | ||||
| } | ||||
| .colorpicker_hue { | ||||
| 	position: absolute; | ||||
| 	top: 13px; | ||||
| 	left: 171px; | ||||
| 	width: 35px; | ||||
| 	height: 150px; | ||||
| 	cursor: n-resize; | ||||
| } | ||||
| .colorpicker_hue div { | ||||
| 	position: absolute; | ||||
| 	width: 35px; | ||||
| 	height: 9px; | ||||
| 	overflow: hidden; | ||||
| 	background: image-url("colourPicker/colorpicker_indic.gif") left top; | ||||
| 	margin: -4px 0 0 0; | ||||
| 	left: 0px; | ||||
| } | ||||
| .colorpicker_new_color { | ||||
| 	position: absolute; | ||||
| 	width: 60px; | ||||
| 	height: 30px; | ||||
| 	left: 213px; | ||||
| 	top: 13px; | ||||
| 	background: #f00; | ||||
| } | ||||
| .colorpicker_current_color { | ||||
| 	position: absolute; | ||||
| 	width: 60px; | ||||
| 	height: 30px; | ||||
| 	left: 283px; | ||||
| 	top: 13px; | ||||
| 	background: #f00; | ||||
| } | ||||
| .colorpicker input { | ||||
| 	background-color: transparent; | ||||
| 	border: 1px solid transparent; | ||||
| 	position: absolute; | ||||
| 	font-size: 10px; | ||||
| 	font-family: Arial, Helvetica, sans-serif; | ||||
| 	color: #898989; | ||||
| 	top: 4px; | ||||
| 	right: 11px; | ||||
| 	text-align: right; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| 	height: 11px; | ||||
| } | ||||
| .colorpicker_hex { | ||||
| 	position: absolute; | ||||
| 	width: 72px; | ||||
| 	height: 22px; | ||||
| 	background: image-url("colourPicker/colorpicker_hex.png") top; | ||||
| 	left: 212px; | ||||
| 	top: 142px; | ||||
| } | ||||
| .colorpicker_hex input { | ||||
| 	right: 6px; | ||||
| } | ||||
| .colorpicker_field { | ||||
| 	height: 22px; | ||||
| 	width: 62px; | ||||
| 	background-position: top; | ||||
| 	position: absolute; | ||||
| } | ||||
| .colorpicker_field span { | ||||
| 	position: absolute; | ||||
| 	width: 12px; | ||||
| 	height: 22px; | ||||
| 	overflow: hidden; | ||||
| 	top: 0; | ||||
| 	right: 0; | ||||
| 	cursor: n-resize; | ||||
| } | ||||
| .colorpicker_rgb_r { | ||||
| 	background-image: image-url("colourPicker/colorpicker_rgb_r.png"); | ||||
| 	top: 52px; | ||||
| 	left: 212px; | ||||
| } | ||||
| .colorpicker_rgb_g { | ||||
| 	background-image: image-url("colourPicker/colorpicker_rgb_g.png"); | ||||
| 	top: 82px; | ||||
| 	left: 212px; | ||||
| } | ||||
| .colorpicker_rgb_b { | ||||
| 	background-image: image-url("colourPicker/colorpicker_rgb_b.png"); | ||||
| 	top: 112px; | ||||
| 	left: 212px; | ||||
| } | ||||
| .colorpicker_hsb_h { | ||||
| 	background-image: image-url("colourPicker/colorpicker_hsb_h.png"); | ||||
| 	top: 52px; | ||||
| 	left: 282px; | ||||
| } | ||||
| .colorpicker_hsb_s { | ||||
| 	background-image: image-url("colourPicker/colorpicker_hsb_s.png"); | ||||
| 	top: 82px; | ||||
| 	left: 282px; | ||||
| } | ||||
| .colorpicker_hsb_b { | ||||
| 	background-image: image-url("colourPicker/colorpicker_hsb_b.png"); | ||||
| 	top: 112px; | ||||
| 	left: 282px; | ||||
| } | ||||
| .colorpicker_submit { | ||||
| 	position: absolute; | ||||
| 	width: 22px; | ||||
| 	height: 22px; | ||||
| 	background: image-url("colourPicker/colorpicker_submit.png") top; | ||||
| 	left: 322px; | ||||
| 	top: 142px; | ||||
| 	overflow: hidden; | ||||
| } | ||||
| .colorpicker_focus { | ||||
| 	background-position: center; | ||||
| } | ||||
| .colorpicker_hex.colorpicker_focus { | ||||
| 	background-position: bottom; | ||||
| } | ||||
| .colorpicker_submit.colorpicker_focus { | ||||
| 	background-position: bottom; | ||||
| } | ||||
| .colorpicker_slider { | ||||
| 	background-position: bottom; | ||||
| } | ||||