2872: Add selection checkboxes to folder view. Compare pipeline instances and remove...
authorTom Clegg <tom@curoverse.com>
Tue, 3 Jun 2014 08:17:32 +0000 (04:17 -0400)
committerTom Clegg <tom@curoverse.com>
Tue, 3 Jun 2014 08:17:32 +0000 (04:17 -0400)
18 files changed:
apps/workbench/app/assets/javascripts/pipeline_instances.js
apps/workbench/app/assets/javascripts/selection.js
apps/workbench/app/controllers/folders_controller.rb
apps/workbench/app/helpers/application_helper.rb
apps/workbench/app/views/application/_choose.html.erb
apps/workbench/app/views/application/_content.html.erb
apps/workbench/app/views/collections/_choose_rows.html.erb
apps/workbench/app/views/folders/_choose.html.erb
apps/workbench/app/views/folders/_index_jobs_and_pipelines.html.erb
apps/workbench/app/views/folders/_show_contents.html.erb
apps/workbench/app/views/folders/_show_contents_rows.html.erb
apps/workbench/app/views/folders/_show_permissions.html.erb
apps/workbench/app/views/folders/index.html.erb
apps/workbench/app/views/folders/remove_items.js.erb [moved from apps/workbench/app/views/folders/remove_item.js.erb with 100% similarity]
apps/workbench/app/views/layouts/application.html.erb
apps/workbench/app/views/pipeline_instances/_show_recent.html.erb
apps/workbench/app/views/users/_home.html.erb
apps/workbench/config/routes.rb

index d23ced7fbc31335cd5d44217f4448bf5e1f2398b..614f45fa582e704faed08205d3cb0429484bf34f 100644 (file)
@@ -72,3 +72,20 @@ $(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(e
       $(this).append(parsedData.summary + "<br/>");
     }
 });
+
+var showhide_compare = function() {
+    var form = $('form#compare')[0];
+    $('input[type=hidden][name="uuids[]"]', form).remove();
+    $('input[type=submit]', form).prop('disabled',true).show();
+    var checked_inputs = $('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]:checked');
+    if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
+        checked_inputs.each(function(){
+            if(this.checked) {
+                $('input[type=submit]', form).prop('disabled',false).show();
+                $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
+            }
+        });
+    }
+};
+$('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]').on('click', showhide_compare);
+showhide_compare();
index 1e32c635642ef3fd9f109df931aebc23f8a8013b..59f0fd184f3b1adab6c69a0e4f0a71ab8c51bb89 100644 (file)
@@ -100,13 +100,11 @@ jQuery(function($){
 
         $('.remove-selection').on('click', remove_selection_click);
         $('#clear_selections_button').on('click', clear_selections);
+        $(document).trigger('selections-updated', [lst]);
     };
 
     $(document).
         on('change', '.persistent-selection:checkbox', function(e) {
-            //console.log($(this));
-            //console.log($(this).val());
-
             var inc = 0;
             if ($(this).is(":checked")) {
                 add_selection($(this).val(), $(this).attr('friendly_name'), $(this).attr('href'), $(this).attr('friendly_type'));
@@ -116,7 +114,6 @@ jQuery(function($){
             }
         });
 
-
     $(window).on('load storage', update_count);
 
     $('#selection-form-content').on("click", function(e) {
@@ -178,3 +175,42 @@ select_form_sources = null;
         return ret;
     };
 })();
+
+function dispatch_selection_action() {
+    // Build a new "href" attribute for this link by starting with the
+    // "data-href" attribute and appending ?foo[]=bar&foo[]=baz (or
+    // &foo=... as appropriate) to reflect the current object
+    // selections.
+    var data = [];
+    var param_name = $(this).attr('data-selection-param-name');
+    var href = $(this).attr('data-href');
+    $('.persistent-selection:checkbox:checked').each(function() {
+        data.push({name: param_name, value: $(this).val()});
+    });
+    if (href.indexOf('?') >= 0)
+        href += '&';
+    else
+        href += '?';
+    href += $.param(data, true);
+    $(this).attr('href', href);
+    return true;
+}
+
+function enable_disable_selection_actions() {
+    var $checked = $('.persistent-selection:checkbox:checked');
+    $('[data-selection-action]').
+        closest('div.btn-group-sm').
+        find('*').
+        prop('disabled', ($checked.length == 0));
+    $('[data-selection-action=compare]').
+        closest('li').
+        toggleClass('disabled',
+                    ($checked.filter('[value*=-d1hrv-]').length < 2) ||
+                    ($checked.not('[value*=-d1hrv-]').length > 0));
+}
+
+$(document).
+    on('selections-updated ready ajax:complete', function() {
+        $('[data-selection-action]').click(dispatch_selection_action);
+        enable_disable_selection_actions();
+    });
index 637ae3642bf4ad82abb5b99e88e0db41e23d383a..000cbc90040c5d1cdb3557d32a322e4d3fea7448 100644 (file)
@@ -12,32 +12,39 @@ class FoldersController < ApplicationController
   end
 
   def remove_item
+    params[:item_uuids] = [params[:item_uuid]]
+    remove_items
+    render template: 'folders/remove_items'
+  end
+
+  def remove_items
     @removed_uuids = []
     links = []
-    item = ArvadosBase.find params[:item_uuid]
-    if (item.class == Link and
-        item.link_class == 'name' and
-        item.tail_uuid = @object.uuid)
-      # Given uuid is a name link, linking an object to this
-      # folder. First follow the link to find the item we're removing,
-      # then delete the link.
-      links << item
-      item = ArvadosBase.find item.head_uuid
-    else
-      # Given uuid is an object. Delete all names.
-      links += Link.where(tail_uuid: @object.uuid,
-                          head_uuid: item.uuid,
-                          link_class: 'name')
-    end
-    links.each do |link|
-      @removed_uuids << link.uuid
-      link.destroy
-    end
-    if item.owner_uuid == @object.uuid
-      # Object is owned by this folder. Remove it from the folder by
-      # changing owner to the current user.
-      item.update_attributes owner_uuid: current_user.uuid
-      @removed_uuids << item.uuid
+    params[:item_uuids].collect { |uuid| ArvadosBase.find uuid }.each do |item|
+      if (item.class == Link and
+          item.link_class == 'name' and
+          item.tail_uuid == @object.uuid)
+        # Given uuid is a name link, linking an object to this
+        # folder. First follow the link to find the item we're removing,
+        # then delete the link.
+        links << item
+        item = ArvadosBase.find item.head_uuid
+      else
+        # Given uuid is an object. Delete all names.
+        links += Link.where(tail_uuid: @object.uuid,
+                            head_uuid: item.uuid,
+                            link_class: 'name')
+      end
+      links.each do |link|
+        @removed_uuids << link.uuid
+        link.destroy
+      end
+      if item.owner_uuid == @object.uuid
+        # Object is owned by this folder. Remove it from the folder by
+        # changing owner to the current user.
+        item.update_attributes owner_uuid: current_user.uuid
+        @removed_uuids << item.uuid
+      end
     end
   end
 
index d9e97b95f75c909ee059ce4a306c4186d6acc1b3..5eed7f8f3d9576ce62e0fae64107134e35cf10d4 100644 (file)
@@ -180,7 +180,7 @@ module ApplicationHelper
       :class => "editable"
     }.merge(htmloptions).merge(ajax_options)
     edit_button = raw('<a href="#" class="btn btn-xs btn-default btn-nodecorate" data-toggle="x-editable tooltip" data-toggle-selector="#' + span_id + '" data-placement="top" title="' + (htmloptions[:tiptitle] || 'edit') + '"><i class="fa fa-fw fa-pencil"></i></a>')
-    if htmloptions[:tipplacement] == :left
+    if htmloptions[:btnplacement] == :left
       edit_button + ' ' + span_tag
     else
       span_tag + ' ' + edit_button
index 4708ac51e51d7ee89bb713158bbb0b46f6a3db9f..c42adacc8840b5bf136a55a4188848abbde8236e 100644 (file)
@@ -6,7 +6,7 @@
         <h4 class="modal-title"><%= params[:title] || "Choose #{@objects.first.class_for_display}" %></h4>
       </div>
       <div class="modal-body">
-        <div class="container-fluid">
+        <div>
           <div class="row">
             <div class="col-sm-6">
             </div>
index b41da15c2fb9e7767e0056e53bbcd0526f655a41..0e3fa0b9789ed386643c7cc69b8f854952a35ba0 100644 (file)
@@ -36,7 +36,7 @@
 <div class="tab-content">
 <% panes.each_with_index do |(pane, content), i| %>
   <div id="<%= pane %>" class="tab-pane fade <%= 'in active' if i==0 %>">
-    <div id="<%= pane %>-scroll" class="smart-scroll" style="margin-top:0.5em;">
+    <div id="<%= pane %>-scroll" class="<%= 'smart-scroll' if pane.match(/graph/) %>" style="margin-top:0.5em;">
       <%= content %>
     </div>
   </div>
index 88d71d2ff78a941817b898fc805766ef43b99c29..d48874353dfa80d23670a5a23ad8c3f9bea75a60 100644 (file)
@@ -4,7 +4,7 @@
       <i class="fa fa-fw fa-archive"></i>
       <%= name_link.name %>
     </div>
-    <div class="col-sm-11 col-sm-push-1 arv-description-in-table">
+    <div class="col-sm-11 col-sm-push-1" style="overflow-x: hidden">
       <%= render_controller_partial(
           'show_object_description_cell.html',
           controller_name: 'collections',
index 7d57ca55f35a8f496b8740cd7be001a6e4d8d5b7..2262c079c925bc82dd37b34d4ec8a78c8ae7caa4 100644 (file)
@@ -8,7 +8,7 @@
       </div>
 
       <div class="modal-body">
-        <div class="container-fluid selectable-container" style="height: 15em; overflow-y: scroll">
+        <div class="selectable-container" style="height: 15em; overflow-y: scroll">
           <% [@my_folder_tree, @shared_folder_tree].each do |tree| %>
             <% tree.each do |foldernode| %>
               <% if foldernode[:object].is_a? String %>
index fb637c6e259c702d921f4276db556adde4a5a23d..4c066f9d736b243842920760b04234349c3e10df 100644 (file)
@@ -1,4 +1,4 @@
-<div class="container-fluid">
+<div>
   <% any = false %>
   <% recent_jobs_and_pipelines[0..9].each do |object| %>
     <% any = true %>
@@ -6,7 +6,7 @@
       <div class="col-sm-4">
         <%= render :partial => "show_object_button", :locals => {object: object, size: 'xs'} %>
         <% if object.respond_to?(:name) %>
-          <%= render_editable_attribute object, 'name', nil %>
+          <%= render_editable_attribute object, 'name', nil, {tiptitle: 'rename', btnplacement: :left} %>
         <% else %>
           <%= object.class_for_display %> <%= object.uuid %>
         <% end %>
index 68237f8485b4488f71b63546fe9b3abede867eae..834d070434f8db8069efabcb61f8898d2ec53e4b 100644 (file)
   <% end %>
 <% end %>
 
-<div class="container-fluid">
+<div class="selection-action-container">
   <div class="row">
-    <div class="col-md-4">
+    <div class="col-sm-5">
+      <div class="btn-group btn-group-sm">
+        <button type="button" class="btn btn-default">Selection...</button>
+        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+          <span class="caret"></span>
+          <span class="sr-only">Toggle Dropdown</span>
+        </button>
+        <ul class="dropdown-menu" role="menu">
+          <li><%= link_to "Compare selected", '#',
+                  'data-href' => compare_pipeline_instances_path,
+                  'data-selection-param-name' => 'uuids[]',
+                  'data-selection-action' => 'compare'
+            %></li>
+          <li><%= link_to "Remove selected", '#',
+                  'data-href' => url_for(action: :remove_items),
+                  'data-selection-param-name' => 'item_uuids[]',
+                  'data-selection-action' => 'remove',
+                  'data-remote' => true,
+                  'method' => 'delete'
+            %></li>
+        </ul>
+      </div>
       <% if @object.editable? %>
         <%= link_to(
               choose_collections_path(
         <% end %>
       <% end %>
     </div>
-    <div class="col-md-4">
+    <div class="col-sm-3">
       <form class="form-inline" role="form">
         Show:
-        <select class="form-control filterable-control" data-filterable-attribute="data-kind" data-filterable-target="table.arv-index.arv-folder-contents tbody">
+        <select class="form-control form-control-sm filterable-control" data-filterable-attribute="data-kind" data-filterable-target="table.arv-index.arv-folder-contents tbody">
           <option value="">Everything</option>
           <option value="arvados#collection">Data</option>
           <option value="arvados#pipelineInstance arvados#job">Compute jobs</option>
         </select>
       </form>
     </div>
-    <div class="col-md-4">
+    <div class="col-sm-4">
       <input type="text" class="form-control filterable-control" placeholder="Search folder contents" data-filterable-target="table.arv-index.arv-folder-contents tbody"/>
     </div>
   </div>
-</div>
 
-<table class="table table-condensed table-fixedlayout arv-index arv-folder-contents" style="overflow-x: hidden">
-  <colgroup>
-    <col width="40%" />
-    <col width="60%" />
-  </colgroup>
-  <tbody data-infinite-scroller="#Contents-scroll" data-infinite-content-href="<%= url_for(format: :json, partial: :contents_rows, offset: next_page_offset) if next_page_offset %>">
-    <%= render partial: 'show_contents_rows', locals: {folder: @object, objects_and_names: @objects_and_names} %>
-  </tbody>
-  <thead>
-    <tr>
-      <th>
-      </th>
-      <th>
-        description
-      </th>
-    </tr>
-  </thead>
-</table>
+  <table class="table table-condensed table-fixedlayout arv-index arv-folder-contents" style="overflow-x: hidden">
+    <colgroup>
+      <col width="40%" />
+      <col width="60%" />
+    </colgroup>
+    <tbody data-infinite-scroller="#Contents-scroll" data-infinite-content-href="<%= url_for(format: :json, partial: :contents_rows, offset: next_page_offset) if next_page_offset %>">
+      <%= render partial: 'show_contents_rows', locals: {folder: @object, objects_and_names: @objects_and_names} %>
+    </tbody>
+    <thead>
+      <tr>
+        <th>
+        </th>
+        <th>
+          description
+        </th>
+      </tr>
+    </thead>
+  </table>
+
+</div>
index 6aa0a877faaa297fdc9dede9e95355741b59acf5..1cdd48f3591738b23bb3f1b6cdf0ff209750e9c9 100644 (file)
@@ -5,6 +5,8 @@
       data-kind="<%= object.kind %>"
       >
     <td>
+      <%= render partial: 'selection_checkbox', locals: {object: object, friendly_name: (name_object.name rescue nil)} %>
+
       <% if folder.editable? %>
         <%= link_to({action: 'remove_item', id: folder.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "Remove #{object.class_for_display.downcase} #{name_object.name rescue object.uuid} from this folder?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: 'remove') do %>
           <i class="fa fa-fw fa-ban"></i>
@@ -15,7 +17,7 @@
 
       <%= render :partial => "show_object_button", :locals => {object: object, size: 'sm'} %>
 
-      <%= render_editable_attribute name_object, 'name', nil, {tipplacement: :left, tiptitle: 'rename'} %>
+      <%= render_editable_attribute name_object, 'name', nil, {btnplacement: :left, tiptitle: 'rename'} %>
     </td>
     <td class="arv-description-in-table">
       <%= render_controller_partial(
index 262cb3bdfbf87900053e363d600c1cc486f4059c..0eca2283cc50de6237e18ad8813689e1345be7d2 100644 (file)
@@ -1,4 +1,4 @@
-<div class="container-fluid">
+<div>
   <div class="row">
     <div class="col-md-6">
       <div class="panel panel-default">
index 5197f8467716c58f3d21dc27241e64c85e51aabb..ce5220bb630e1cdeb794e030c90c03f6477dcc47 100644 (file)
@@ -3,7 +3,7 @@
 <li><a href="#">Home</a></li>
 <% end %>
 
-<div class="container-fluid">
+<div>
   <div class="row">
     <div class="col-sm-6">
       <% if my_folders.empty? %>
index 631595fb0920237eeed4b94b4368e9a718e8aa27..6359794312f2c3dc0713d1e00d579b524d2b0fb8 100644 (file)
     height: 100%;
     }
 
-    body > div.container-fluid {
-    padding-top: 70px; /* 70px to make the container go all the way to the bottom of the navbar */
-    }
-
     @media (max-width: 979px) { body { padding-top: 0; } }
 
     .navbar .nav li.nav-separator > span.glyphicon.glyphicon-arrow-right {
@@ -51,7 +47,7 @@
   <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
 </head>
 <body>
-  <div id="wrapper">
+  <div id="wrapper" class="container-fluid">
     <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
       <div class="navbar-header">
         <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
index e9a01dc253c1958e505195eba36e61d3aca75975..86eab623f6595508043ee31634580435056caceb 100644 (file)
 <% end %>
 
 <%= render partial: "paging", locals: {results: @objects, object: @object} %>
-
-<% content_for :footer_js do %>
-var showhide_compare = function() {
-    var form = $('form#compare')[0];
-    $('input[type=hidden][name="uuids[]"]', form).remove();
-    $('input[type=submit]', form).prop('disabled',true).show();
-    var checked_inputs = $('input[name="uuids[]"]:checked');
-    if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
-        checked_inputs.each(function(){
-            if(this.checked) {
-                $('input[type=submit]', form).prop('disabled',false).show();
-                $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
-            }
-        });
-    }
-};
-$('form input[name="uuids[]"]').on('click', showhide_compare);
-showhide_compare();
-<% end %>
index 5e8b3f8a9440213dbc55e1b68146af1a4cccfe0e..0c5739c15873b13130307818dac9e7bd5f2befce 100644 (file)
@@ -27,7 +27,7 @@
       }
 <% end %>
 
-<div class="container-fluid" id="home-tables">
+<div id="home-tables">
 
     <%= render :partial => 'tables' %>
 
index 971b0ce7307c0ee6fc8dd6443fe4bff966aa891f..95de0e0f9484bb8271871f9d173c3c9f576e0b00 100644 (file)
@@ -57,6 +57,7 @@ ArvadosWorkbench::Application.routes.draw do
   get '/collections/:uuid/*file' => 'collections#show_file', :format => false
   resources :folders do
     match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
+    match 'remove_items', on: :member, via: :delete, action: :remove_items
     get 'choose', on: :collection
   end