Browse Source

HUE-2254 [fb] Revamp UI and make it more usable

Cleaned up toolbar buttons, consolidated into "Actions" dropdown button
Added ability to change owner, group and permissions by clicking on linked text of selected row
Created context menu markup that is displayed when rows in the view model are right/context clicked
Selected rows have darker background color
Paul McCaughtry 11 years ago
parent
commit
020ba0b356

+ 10 - 0
apps/filebrowser/src/filebrowser/static/css/listdir_components.css

@@ -212,3 +212,13 @@
   text-align: left;
   display: none;
 }
+
+.row-selected {
+  background: #e5e5e5;
+}
+
+.context-menu {
+  display: none;
+  position: absolute;
+  width: 250px;
+}

+ 24 - 19
apps/filebrowser/src/filebrowser/templates/listdir.mako

@@ -44,35 +44,40 @@ ${ fb_components.menubar() }
 
       <%def name="actions()">
         <div class="btn-toolbar" style="display: inline; vertical-align: middle">
-          <button class="btn fileToolbarBtn" title="${_('Rename')}" data-bind="visible: !inTrash(), click: renameFile, enable: selectedFiles().length == 1 && isCurrentDirSelected().length == 0"><i class="fa fa-font"></i> ${_('Rename')}</button>
-          <button class="btn fileToolbarBtn" title="${_('Move')}" data-bind="click: move, enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0"><i class="fa fa-random"></i> ${_('Move')}</button>
-          <button class="btn fileToolbarBtn" title="${_('Copy')}" data-bind="click: copy, enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0"><i class="fa fa-files-o"></i> ${_('Copy')}</button>
-          %if is_fs_superuser:
           <div id="ch-dropdown" class="btn-group" style="vertical-align: middle">
-            <button href="#" class="btn dropdown-toggle" title="${_('Chown / Chmod')}" data-toggle="dropdown" data-bind="visible: !inTrash(), enable: selectedFiles().length > 0">
-              <i class="fa fa-user"></i> ${_('Chmod / Chown')}
+            <button href="#" class="btn dropdown-toggle" title="${_('Actions')}" data-toggle="dropdown"
+            data-bind="visible:
+             !inTrash(), enable: selectedFiles().length > 0">
+              <i class="fa fa-cog"></i> ${_('Actions')}
               <span class="caret" style="line-height: 15px"></span>
             </button>
             <ul class="dropdown-menu" style="top: auto">
+              <li><a href="#" title="${_('Rename')}" data-bind="visible: !inTrash() && selectedFiles().length == 1, click: renameFile,
+              enable: selectedFiles().length == 1 && isCurrentDirSelected().length == 0"><i class="fa fa-font"></i>
+              ${_('Rename')}</a></li>
+              <li><a href="#"title="${_('Move')}" data-bind="click: move, enable: selectedFiles().length > 0 &&
+              isCurrentDirSelected().length == 0"><i class="fa fa-random"></i> ${_('Move')}</a></li>
+              <li><a href="#" title="${_('Copy')}" data-bind="click: copy, enable: selectedFiles().length > 0 &&
+              isCurrentDirSelected().length == 0"><i class="fa fa-files-o"></i> ${_('Copy')}</a></li>
+              <li><a href="#" title="${_('Download')}" data-bind="visible: !inTrash() && selectedFiles().length == 1 && selectedFile().type == 'file', click: downloadFile"><i class="fa fa-arrow-circle-o-down"></i> ${_('Download')}</a></li>
+              <li class="divider"></li>
+              %if is_fs_superuser:
               <li><a href="#" title="${_('Change owner/group')}" data-bind="visible: !inTrash(), click: changeOwner, enable: selectedFiles().length > 0"><i class="fa fa-user"></i> ${_('Change owner / group')}</a></li>
+              %endif
               <li><a href="#" title="${_('Change permissions')}" data-bind="visible: !inTrash(), click: changePermissions, enable: selectedFiles().length > 0"><i class="fa fa-list-alt"></i> ${_('Change permissions')}</a></li>
             </ul>
           </div>
-          %else:
-            <button class="btn fileToolbarBtn" title="${_('Change permissions')}" data-bind="visible: !inTrash(), click: changePermissions, enable: selectedFiles().length > 0"><i class="fa fa-list-alt"></i> ${_('Change permissions')}</button>
-          %endif
-          <button class="btn fileToolbarBtn" title="${_('Download')}" data-bind="visible: !inTrash(), click: downloadFile, enable: selectedFiles().length == 1 && selectedFile().type == 'file'"><i class="fa fa-arrow-circle-o-down"></i> ${_('Download')}</button>
           <button class="btn fileToolbarBtn" title="${_('Restore from trash')}" data-bind="visible: inRestorableTrash(), click: restoreTrashSelected, enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0"><i class="fa fa-cloud-upload"></i> ${_('Restore')}</button>
           <!-- ko ifnot: inTrash -->
-              <div id="delete-dropdown" class="btn-group" style="vertical-align: middle">
-                <button id="trash-btn" class="btn toolbarBtn" data-bind="enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0, click: trashSelected"><i class="fa fa-times"></i> ${_('Move to trash')}</button>
-                <button id="trash-btn-caret" class="btn toolbarBtn dropdown-toggle" data-toggle="dropdown" data-bind="enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0">
-                  <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu">
-                  <li><a href="#" class="delete-link" title="${_('Delete forever')}" data-bind="enable: selectedFiles().length > 0, click: deleteSelected"><i class="fa fa-bolt"></i> ${_('Delete forever')}</a></li>
-                </ul>
-              </div>
+          <div id="delete-dropdown" class="btn-group" style="vertical-align: middle">
+            <button id="trash-btn" class="btn toolbarBtn" data-bind="enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0, click: trashSelected"><i class="fa fa-times"></i> ${_('Move to trash')}</button>
+            <button id="trash-btn-caret" class="btn toolbarBtn dropdown-toggle" data-toggle="dropdown" data-bind="enable: selectedFiles().length > 0 && isCurrentDirSelected().length == 0">
+              <span class="caret"></span>
+            </button>
+            <ul class="dropdown-menu">
+              <li><a href="#" class="delete-link" title="${_('Delete forever')}" data-bind="enable: selectedFiles().length > 0, click: deleteSelected"><i class="fa fa-bolt"></i> ${_('Delete forever')}</a></li>
+            </ul>
+          </div>
           <!-- /ko -->
           <button class="btn fileToolbarBtn" title="${_('Submit')}"
             data-bind="visible: selectedFiles().length == 1 && $.inArray(selectedFile().name, ['workflow.xml', 'coordinator.xml', 'bundle.xml']) > -1, click: submitSelected">

+ 112 - 25
apps/filebrowser/src/filebrowser/templates/listdir_components.mako

@@ -395,6 +395,29 @@ from django.utils.translation import ugettext as _
     </form>
   </div>
 
+  <!-- actions context menu -->
+  <ul class="context-menu dropdown-menu">
+    <li><a href="#" title="${_('Rename')}" data-bind="visible: !$root.inTrash() && $root.selectedFiles().length == 1, click: $root.renameFile,
+    enable: $root.selectedFiles().length == 1 && isCurrentDirSelected().length == 0"><i class="fa fa-font"></i>
+    ${_('Rename')}</a></li>
+    <li><a href="#"title="${_('Move')}" data-bind="click: $root.move, enable: $root.selectedFiles().length > 0 &&
+    isCurrentDirSelected().length == 0"><i class="fa fa-random"></i> ${_('Move')}</a></li>
+    <li><a href="#" title="${_('Copy')}" data-bind="click: $root.copy, enable: $root.selectedFiles().length > 0 &&
+    isCurrentDirSelected().length == 0"><i class="fa fa-files-o"></i> ${_('Copy')}</a></li>
+    <li><a href="#" title="${_('Download')}" data-bind="visible: !$root.inTrash() && $root.selectedFiles().length == 1 && selectedFile().type == 'file', click: $root.downloadFile"><i class="fa fa-arrow-circle-o-down"></i> ${_('Download')}</a></li>
+    <li class="divider"></li>
+    %if is_fs_superuser:
+    <li><a href="#" title="${_('Change owner/group')}" data-bind="visible: !$root.inTrash(), click: $root.changeOwner, enable: $root.selectedFiles().length > 0"><i class="fa fa-user"></i> ${_('Change owner / group')}</a></li>
+    %endif
+    <li><a href="#" title="${_('Change permissions')}" data-bind="visible: !$root.inTrash(), click: $root.changePermissions, enable: $root.selectedFiles().length > 0"><i class="fa fa-list-alt"></i> ${_('Change permissions')}</a></li>
+    <li class="divider"></li>
+    <!-- ko ifnot: $root.inTrash -->
+    <li><a href="#"  data-bind="enable: $root.selectedFiles().length > 0 && isCurrentDirSelected().length == 0,
+    click: $root.trashSelected"><i class="fa fa-times"></i> ${_('Move to trash')}</a></li>
+    <li><a href="#" class="delete-link" title="${_('Delete forever')}" data-bind="enable: $root.selectedFiles().length > 0, click: $root.deleteSelected"><i class="fa fa-bolt"></i> ${_('Delete forever')}</a></li>
+    <!-- /ko -->
+  </ul>
+
   <div id="submit-wf-modal" class="modal hide"></div>
 
   <div id="progressStatus" class="uploadstatus alert alert-info hide">
@@ -406,27 +429,43 @@ from django.utils.translation import ugettext as _
 </div>
 
   <script id="fileTemplate" type="text/html">
-    <tr style="cursor: pointer" data-bind="event: { mouseover: toggleHover, mouseout: toggleHover}">
+    <tr style="cursor: pointer" data-bind="event: { mouseover: toggleHover, mouseout: toggleHover, contextmenu: showContextMenu }, click: $root.viewFile, css: { 'row-selected': selected() }">
       <td class="center" data-bind="click: handleSelect" style="cursor: default">
-        <div data-bind="visible: name != '..', css: {hueCheckbox: name != '..', 'fa': name != '..', 'fa-check': selected}"></div>
+        <div data-bind="visible: name != '..', css: { hueCheckbox: name != '..', 'fa': name != '..', 'fa-check': selected }"></div>
       </td>
-      <td data-bind="click: $root.viewFile" class="left"><i data-bind="css: {'fa': true, 'fa-play': $.inArray(name, ['workflow.xml', 'coordinator.xml', 'bundle.xml']) > -1, 'fa-file-o': type == 'file', 'fa-folder': type != 'file', 'fa-folder-open': type != 'file' && hovered}"></i></td>
-      <td data-bind="click: $root.viewFile, attr: {'title': tooltip}" rel="tooltip">
+      <td class="left"><i data-bind="click: $root.viewFile, css: { 'fa': true, 'fa-play': $.inArray(name, ['workflow.xml', 'coordinator.xml', 'bundle.xml']) > -1, 'fa-file-o': type == 'file', 'fa-folder': type != 'file', 'fa-folder-open': type != 'file' && hovered }"></i></td>
+      <td data-bind="attr: {'title': tooltip}" rel="tooltip">
         <!-- ko if: name == '..' -->
         <a href="#" data-bind="click: $root.viewFile"><i class="fa fa-level-up"></i></a>
         <!-- /ko -->
         <!-- ko if: name != '..' -->
         <strong><a href="#" data-bind="click: $root.viewFile, text: name"></a></strong>
         <!-- /ko -->
-
       </td>
-      <td data-bind="click: $root.viewFile">
+      <td>
         <span data-bind="visible: type=='file', text: stats.size"></span>
       </td>
-      <td data-bind="click: $root.viewFile, text: stats.user"></td>
-      <td data-bind="click: $root.viewFile, text: stats.group"></td>
-      <td data-bind="click: $root.viewFile, text: permissions"></td>
-      <td data-bind="click: $root.viewFile, text: stats.mtime" style="white-space: nowrap;"></td>
+      <td>
+        %if is_fs_superuser:
+        <span data-bind="text: stats.user, visible: ! selected()"></span>
+        <a href="#" rel="tooltip" title="${_('Change owner')}" data-original-title="${_('Change owner')}" data-bind="text: stats.user, visible: ! $root.inTrash() && selected(), click: $root.changeOwner, enable: $root.selectedFiles().length > 0"></a>
+        %else:
+        <span data-bind="text: stats.user"></span>
+        %endif
+      </td>
+      <td>
+        %if is_fs_superuser:
+        <span data-bind="text: stats.group, visible: ! selected()"></span>
+        <a href="#" rel="tooltip" title="${_('Change group')}" data-original-title="${_('Change group')}" data-bind="text: stats.group, visible: ! $root.inTrash() && selected(), click: $root.changeOwner"></a>
+        %else:
+        <span data-bind="text: stats.group"></span>
+        %endif
+      </td>
+      <td>
+        <span data-bind="text: permissions, visible: ! selected()"></span>
+        <a href="#" rel="tooltip" title="${_('Change permissions')}" data-bind="text: permissions, visible: ! $root.inTrash() && selected(), click: $root.changePermissions" data-original-title="${_('Change permissions')}"></a>
+      </td>
+      <td data-bind="text: stats.mtime" style="white-space: nowrap;"></td>
     </tr>
   </script>
 
@@ -582,9 +621,37 @@ from django.utils.translation import ugettext as _
         },
         selected:ko.observable(false),
         handleSelect: function (row, e) {
+          e.preventDefault();
+          e.stopPropagation();
           this.selected(! this.selected());
           viewModel.allSelected(false);
         },
+        // display the context menu when an item is right/context clicked
+        showContextMenu: function (row, e) {
+          var cm = $('.context-menu'),
+            actions = $('#ch-dropdown'),
+            rect = document.querySelector('body').getBoundingClientRect();
+
+          // clear out selections
+          ko.utils.arrayFilter(viewModel.files(), function (file) {
+            if (file.selected()) {
+              file.selected(false);
+            }
+          });
+
+          // close the actions menu from button area if open
+          if (actions.hasClass('open')) {
+            actions.removeClass('open');
+          }
+
+          // display context menu and ensure it is on-screen
+          if ($.inArray(row.name, ['..', '.']) === -1) {
+            this.selected(true);
+            cm.css({ display: 'block', top: e.pageY - 15, left: (e.pageX < rect.right - 200 ) ? e.pageX : e.pageX - 250 });
+          } else {
+            cm.css({ display: 'none' });
+          }
+        },
         hovered:ko.observable(false),
         toggleHover: function (row, e) {
           this.hovered(! this.hovered());
@@ -920,8 +987,10 @@ from django.utils.translation import ugettext as _
 
       };
 
-      self.changeOwner = function () {
+      self.changeOwner = function (data, event) {
         var paths = [];
+        event.preventDefault();
+        event.stopPropagation();
 
         $(self.selectedFiles()).each(function (index, file) {
           paths.push(file.path);
@@ -948,10 +1017,13 @@ from django.utils.translation import ugettext as _
         });
       };
 
-      self.changePermissions = function () {
+      self.changePermissions = function (data, event) {
         var paths = [];
         var allFileType = true;
 
+        event.preventDefault();
+        event.stopPropagation();
+
         $(self.selectedFiles()).each(function (index, file) {
           if ("dir" == file.type){
             allFileType = false;
@@ -1194,6 +1266,34 @@ from django.utils.translation import ugettext as _
 
     $(document).ready(function () {
 
+      if (getHistory().length == 0) {
+        $('.history').addClass('no-history');
+      }
+
+      $('.historyLink').on('click', function (e) {
+        if(getHistory().length > 0) {
+          showHistory();
+        } else {
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      });
+
+      // hide context menu
+      $('body').on('click', function (e) {
+        var cm = $('.context-menu');
+        if (cm.is(':visible')) {
+          cm.css({ display: 'none' });
+        }
+      });
+
+      $('body').on('contextmenu', '.context-menu', function (e) {
+        var cm = $('.context-menu');
+        if (cm.is(':visible')) {
+          cm.css({ display: 'none' });
+        }
+      });
+
       // Drag and drop uploads from anywhere on filebrowser screen
       if (window.FileReader) {
         var showHoverMsg = function (msg) {
@@ -1207,19 +1307,6 @@ from django.utils.translation import ugettext as _
 
         var _isDraggingOverText = false;
 
-        if (getHistory().length == 0) {
-          $('.history').addClass('no-history');
-        }
-
-        $('.historyLink').on('click', function (e) {
-          if(getHistory().length > 0) {
-            showHistory();
-          } else {
-            e.preventDefault();
-            e.stopPropagation();
-          }
-        });
-
         $('.card').on('dragenter', function (e) {
           e.preventDefault();
           showHoverMsg("${_('Drop files here to upload')}");