Prechádzať zdrojové kódy

[notebook] Switch to JS for toggling visibility of actions on hover

With this we can now use an observable to control the visibility of the actions. When using a pure css solution the opacity behaviour is inconsistent across browsers and sometimes changes aren't even picked-up. With a JS approach it's more consistent across browsers.
Johan Ahlen 10 rokov pred
rodič
commit
bc6db6c9b4

+ 22 - 45
desktop/core/src/desktop/static/desktop/css/hue3.css

@@ -2049,51 +2049,6 @@ pre {
   white-space: nowrap;
 }
 
-.reveals-actions:hover .hover-actions,
-.reveals-actions:hover .hover-actions-no-transition,
-.reveals-actions-2nd:hover .hover-actions-2nd,
-.reveals-actions-3rd:hover .hover-actions-3rd {
-  visibility: visible;
-  opacity: 1;
-}
-
-.show-actions {
-  visibility: visible !important;
-  opacity: 1 !important;
-}
-
-.hover-actions,
-.hover-actions-2nd,
-.hover-actions-3rd {
-  display: inline-block;
-  opacity: 0;
-  -webkit-transition: opacity 0.2s linear;
-  -moz-transition: opacity 0.2s linear;
-  -ms-transition: opacity 0.2s linear;
-  transition: opacity 0.2s linear;
-}
-
-/* Work-around for chrome with element out-transition not firing in .dropdown */
-.hover-actions-no-transition {
-  visibility: hidden;
-}
-
-.hover-actions > a,
-.hover-actions-2nd > a,
-.hover-actions-3rd > a {
-  color: #D1D1D1;
-  -webkit-transition: color 0.2s ease;
-  -moz-transition: color 0.2s ease;
-  -ms-transition: color 0.2s ease;
-  transition: color 0.2s ease;
-}
-
-.hover-actions > a:hover,
-.hover-actions-2nd > a:hover,
-.hover-actions-3rd > a:hover {
-  color: #338bb8;
-}
-
 .leaflet-search-command {
   padding: 4px;
   background-color: #FFF;
@@ -2220,3 +2175,25 @@ div.navigator .pull-right .nav {
   border-left: none;
   background: transparent;
 }
+
+.hover-actions {
+  opacity: 0;
+}
+
+.hover-actions * {
+  text-decoration: none !important;
+}
+
+.inactive-action,
+.inactive-action a {
+  -webkit-transition: color 0.2s ease;
+  -moz-transition: color 0.2s ease;
+  -ms-transition: color 0.2s ease;
+  transition: color 0.2s ease;
+  color: #d1d1d1;
+}
+
+.inactive-action:hover,
+.inactive-action:hover > a {
+  color: #338bb8 !important;
+}

+ 46 - 0
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -22,6 +22,52 @@
   }
 }(function (ko) {
 
+  ko.bindingHandlers.visibleOnHover = {
+    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
+      var options = valueAccessor();
+      var $element  = $(element);
+
+      var selector = options.selector;
+      var hideTimeout = -1;
+      var override = false;
+      var inside = false;
+
+      var show = function () {
+        $element.find(selector).fadeTo("normal", 1);
+        clearTimeout(hideTimeout);
+      };
+
+      var hide = function () {
+        hideTimeout = window.setTimeout(function () {
+          $element.find(selector).fadeTo("normal", 0);
+        }, 100);
+      };
+
+      if (ko.isObservable(options.override)) {
+        options.override.subscribe(function (newValue) {
+          override = newValue;
+          if (newValue) {
+            show();
+          } else if (! inside) {
+            hide();
+          }
+        })
+      }
+
+      $element.mouseenter(function () {
+        inside = true;
+        show();
+      });
+
+      $element.mouseleave(function () {
+        inside = false;
+        if (! override) {
+          hide();
+        }
+      });
+    }
+  };
+
   ko.bindingHandlers.slideVisible = {
     init: function (element, valueAccessor) {
       var value = valueAccessor();

+ 8 - 13
desktop/core/src/desktop/templates/ko_components.mako

@@ -43,11 +43,6 @@ from desktop.views import _ko
       padding-bottom: 2px;
     }
 
-    .assist-tables > li:hover .hover-actions-2nd {
-      visibility: visible;
-      opacity: 1;
-    }
-
     .assist-table-link {
       font-size: 13px;
     }
@@ -147,15 +142,15 @@ from desktop.views import _ko
   </script>
 
   <script type="text/html" id="assist-entry-actions">
-    <div class="assist-actions" data-bind="css: { 'hover-actions-2nd': definition.isTable,  'hover-actions-3rd': definition.isColumn, 'show-actions': statsVisible }">
-      <a href="javascript:void(0)" data-bind="visible: definition.isTable, click: showPreview"><i class="fa fa-list" title="${_('Preview Sample data')}"></i></a>
-      <a href="javascript:void(0)" data-bind="visible: definition.isTable || definition.isColumn, click: showStats, css: { 'blue': statsVisible }"><i class='fa fa-bar-chart' title="${_('View statistics') }"></i></a>
+    <div class="assist-actions" data-bind="css: { 'table-actions' : definition.isTable, 'column-actions': definition.isColumn } " style="opacity: 0">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: definition.isTable, click: showPreview"><i class="fa fa-list" title="${_('Preview Sample data')}"></i></a>
+      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: definition.isTable || definition.isColumn, click: showStats, css: { 'blue': statsVisible }"><i class='fa fa-bar-chart' title="${_('View statistics') }"></i></a>
     </div>
   </script>
 
   <script type="text/html" id="assist-entries">
     <ul data-bind="foreach: filteredEntries, css: { 'assist-tables': definition.isDatabase }, event: { 'scroll': assistSource.repositionActions }">
-      <li data-bind="css: { 'assist-table reveals-actions-2nd': definition.isTable, 'assist-column reveals-actions-3rd': definition.isColumn }">
+      <li data-bind="visibleOnHover: { override: statsVisible, selector: definition.isTable ? '.table-actions' : '.column-actions' }, css: { 'assist-table': definition.isTable, 'assist-column': definition.isColumn }">
         <!-- ko template: { if: definition.isTable || definition.isColumn, name: 'assist-entry-actions' } --><!-- /ko -->
         <a class="assist-column-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.title }, css: { 'assist-field-link': ! definition.isTable, 'assist-table-link': definition.isTable }" href="javascript:void(0)">
           <span data-bind="text: definition.displayName"></span>
@@ -183,11 +178,11 @@ from desktop.views import _ko
   </script>
 
   <script type="text/html" id="assist-type-template">
-    <div class="reveals-actions" style="position: relative; width:100%">
+    <div data-bind="visibleOnHover: { selector: '.hover-actions' }" style="position: relative; width:100%">
       <li class="nav-header">
         ${_('database')}
         <div class="pull-right" data-bind="css: { 'hover-actions' : ! reloading() }">
-          <a href="javascript:void(0)" data-bind="click: reloadAssist"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin' : reloading }" title="${_('Manually refresh the table list')}"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: reloadAssist"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin' : reloading }" title="${_('Manually refresh the table list')}"></i></a>
         </div>
       </li>
 
@@ -207,7 +202,7 @@ from desktop.views import _ko
       <li class="nav-header" style="margin-top:10px;" data-bind="visible: ! assistHelper.loading() && ! hasErrors()">
         ${_('tables')}
         <div class="pull-right" data-bind="visible: selectedDatabase() != null && selectedDatabase().hasEntries(), css: { 'hover-actions': ! filter(), 'blue': filter }">
-          <a href="javascript:void(0)" data-bind="click: toggleSearch"><i class="pointer fa fa-search" title="${_('Search')}"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: toggleSearch"><i class="pointer fa fa-search" title="${_('Search')}"></i></a>
         </div>
       </li>
 
@@ -889,7 +884,7 @@ from desktop.views import _ko
       </li>
     </ul>
     <div style="min-width: 280px; margin-top: 5px;">
-      <a class="pointer" style="padding: 3px 10px 3px 3px;;" data-bind="click: addValue">
+      <a class="inactive-action pointer" style="padding: 3px 10px 3px 3px;;" data-bind="click: addValue">
         <i class="fa fa-plus"></i>
       </a>
     </div>

+ 5 - 27
desktop/libs/notebook/src/notebook/static/notebook/css/notebook.css

@@ -327,7 +327,7 @@ body {
   -ms-transition: filter 0.2s ease;
 }
 
-.hover-actions:hover .snippet-icon-image {
+.inactive-action:hover .snippet-icon-image {
   -webkit-filter: grayscale(0)  brightness(1);
   -webkit-filter: grayscale(0%) brightness(100%);
   filter: grayscale(0%) brightness(100%);
@@ -342,28 +342,11 @@ body {
   -webkit-transition: color 0.2s ease;
   -moz-transition: color 0.2s ease;
   -ms-transition: color 0.2s ease;
+  transition: color 0.2s ease;
 }
 
-.hover-actions .editable {
-  color: #D1D1D1;
-}
-
-.hover-actions:hover .editable,
-.hover-actions:hover .snippet-icon {
-  color: #338bb8;
-}
-
-.hover-actions sup,
-.hover-actions .caret,
-.hover-actions .editable {
-  -webkit-transition: color 0.2s ease;
-  -moz-transition: color 0.2s ease;
-  -ms-transition: color 0.2s ease;
-}
-
-.hover-actions:hover sup,
-.hover-actions:hover .caret {
-  color: #338bb8;
+.inactive-action:hover .snippet-icon {
+  color: #338bb8 !important;
 }
 
 .row-container.is-editing {
@@ -563,7 +546,7 @@ h1.empty {
 }
 
 @media print {
-  .navigator, .assist-container, .result-settings, .left-panel, a.btn, button, .add-snippet, .card-heading.simple, .hover-actions, .alert, .show-assist, .snippet-footer-actions-bar, .player-toolbar h4, .player-toolbar div {
+  .navigator, .assist-container, .result-settings, .left-panel, a.btn, button, .add-snippet, .card-heading.simple, .alert, .show-assist, .snippet-footer-actions-bar, .player-toolbar h4, .player-toolbar div {
     display: none !important;
   }
   .right-panel {
@@ -648,11 +631,6 @@ table.airy tr td {
   right: 0;
 }
 
-.hover-actions a {
-  text-decoration: none;
-  cursor: pointer;
-}
-
 .hover-dropdown {
   display: inline-block;
   margin: 0 5px;

+ 23 - 23
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -350,17 +350,17 @@ ${ require.config() }
 
 
 <script type="text/html" id="snippet">
-  <div class="snippet-container reveals-actions row-fluid">
+  <div class="snippet-container row-fluid" data-bind="visibleOnHover: { selector: '.hover-actions' }">
     <div data-bind="css: klass, attr: {'id': 'snippet_' + id()}">
 
       <h5 class="card-heading-print" data-bind="text: name, css: {'visible': name() != ''}"></h5>
 
       <h2 class="card-heading simple" data-bind="dblclick: function(){ $parent.newSnippetAbove(id()) }">
 
-        <div class="hover-actions dropdown inline widget-type">
-          <a class="dropdown-toggle no-underline" data-toggle="dropdown" href="javascript:void(0)">
+        <div class="inactive-action hover-actions dropdown inline widget-type">
+          <a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
             <span data-bind="template: { name: 'snippetIcon', data: $data }"></span>
-            <span class="hover-actions-no-transition">
+            <span>
               <b class="caret"></b>
             </span>
           </a>
@@ -369,13 +369,13 @@ ${ require.config() }
           </ul>
         </div>
 
-        <div class="hover-actions"><span data-bind="editable: name, editableOptions: { emptytext: '${_ko('Untitled')}', mode: 'inline', enabled: true, placement: 'right' }" style="border:none;"></span></div>
+        <div class="inactive-action hover-actions inline"><span data-bind="editable: name, editableOptions: { emptytext: '${_ko('Untitled')}', mode: 'inline', enabled: true, placement: 'right' }" style="border:none;"></span></div>
 
         <div class="hover-actions inline pull-right" style="font-size: 15px;">
-          <a href="javascript:void(0)" class="move-widget"><i class="fa fa-arrows"></i></a>
-          <a href="javascript:void(0)" data-bind="click: function(){ codeVisible(! codeVisible()) }, visible: type() != 'text'"><i class="fa" data-bind="css: {'fa-compress' : codeVisible, 'fa-expand' : ! codeVisible() }"></i></a>
-          <a href="javascript:void(0)" data-bind="click: function(){ settingsVisible(! settingsVisible()) }, visible: hasProperties, css: { 'blue' : settingsVisible }"><i class="fa fa-cog"></i></a>
-          <a href="javascript:void(0)" data-bind="click: function(){ $root.removeSnippet($parent, $data); }"><i class="fa fa-times"></i></a>
+          <a class="inactive-action move-widget" href="javascript:void(0)"><i class="fa fa-arrows"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: function(){ codeVisible(! codeVisible()) }, visible: type() != 'text'"><i class="fa" data-bind="css: {'fa-compress' : codeVisible, 'fa-expand' : ! codeVisible() }"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: function(){ settingsVisible(! settingsVisible()) }, visible: hasProperties, css: { 'blue' : settingsVisible }"><i class="fa fa-cog"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: function(){ $root.removeSnippet($parent, $data); }"><i class="fa fa-times"></i></a>
         </div>
 
       </h2>
@@ -421,14 +421,14 @@ ${ require.config() }
 </script>
 
 <script type="text/html" id="property">
-  <div class="reveals-actions" data-bind="css: { 'spark-property' : typeof inline === 'undefined' || inline, 'control-group' : typeof inline !== 'undefined' && ! inline }">
+  <div data-bind="visibleOnHover: { selector: '.hover-actions' }, css: { 'spark-property' : typeof inline === 'undefined' || inline, 'control-group' : typeof inline !== 'undefined' && ! inline }">
     <label class="control-label" data-bind="text: label, style: { 'width' : typeof inline === 'undefined' || inline ? '120px' : '' }"></label>
     <div class="controls" style="margin-right:10px;" data-bind="style: { 'margin-left' : typeof inline === 'undefined' || inline ? '140px' : '' }">
       <!-- ko template: { name: 'property-' + type } --><!-- /ko -->
     </div>
     <!-- ko ifnot: typeof remove === "undefined" -->
     <div class="hover-actions spark-property-remove">
-      <a href="javascript:void(0)" data-bind="click: remove" title="${ _('Remove') }">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: remove" title="${ _('Remove') }">
         <i class="fa fa-times"></i>
       </a>
     </div>
@@ -541,10 +541,10 @@ ${ require.config() }
       </div>
       <div data-bind="css: {'span10': isResultSettingsVisible, 'span12 nomargin': !isResultSettingsVisible()}">
         <div data-bind="attr: { 'id': 'toggleResultSettingsGrid' + id() }, click: toggleResultSettings" class="toggle-result-settings show-result-settings">
-          <a title="${_('Show columns')}" class="pointer" data-bind="visible: !isResultSettingsVisible()">
+          <a class="inactive-action pointer" title="${_('Show columns')}" data-bind="visible: !isResultSettingsVisible()">
             <i class="fa fa-chevron-right"></i>
           </a>
-          <a title="${_('Hide')}" class="pointer" data-bind="visible: isResultSettingsVisible">
+          <a class="inactive-action pointer" title="${_('Hide')}" data-bind="visible: isResultSettingsVisible">
             <i class="fa fa-chevron-left"></i>
           </a>
         </div>
@@ -564,8 +564,8 @@ ${ require.config() }
 
     <div data-bind="visible: showChart" style="display:none;">
       <div data-bind="visible: isResultSettingsVisible, css:{'span2 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}">
-        <div style="float: right; margin-right: -30px; margin-top:0" data-bind="attr: { 'class': 'hover-actions toggle-result-settings toggleResultSettingsChart' + id() }, click: toggleResultSettings">
-          <a title="${_('Hide settings')}" class="pointer">
+        <div style="float: right; margin-right: -30px; margin-top:0" data-bind="attr: { 'class': 'hover-actions inline toggle-result-settings toggleResultSettingsChart' + id() }, click: toggleResultSettings">
+          <a class="inactive-action" title="${_('Hide settings')}" class="pointer">
             <i class="fa fa-chevron-left"></i>
           </a>
         </div>
@@ -652,8 +652,8 @@ ${ require.config() }
       </div>
 
       <div data-bind="css:{'span10 chart-container': isResultSettingsVisible, 'span12 nomargin chart-container': !isResultSettingsVisible() }">
-        <div style="margin-right: -30px; margin-top:0" data-bind="visible: !isResultSettingsVisible(), click: toggleResultSettings, attr: { 'class': 'hover-actions toggle-result-settings toggleResultSettingsChart' + id()}">
-          <a title="${_('Show settings')}" class="pointer">
+        <div style="margin-right: -30px; margin-top:0" data-bind="visible: !isResultSettingsVisible(), click: toggleResultSettings, attr: { 'class': 'hover-actions inline toggle-result-settings toggleResultSettingsChart' + id()}">
+          <a class="inactive-action" title="${_('Show settings')}" class="pointer">
             <i class="fa fa-chevron-right"></i>
           </a>
         </div>
@@ -819,10 +819,10 @@ ${ require.config() }
       </div>
     </div>
 
-    <div class="pull-right hover-actions" style="padding-top: 8px; font-size: 15px;">
+    <div class="pull-right hover-actions inline" style="padding-top: 8px; font-size: 15px;">
       <span style="color: #CCC; padding-right: 10px;" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()"></span>
 
-      <a href="javascript:void(0)" data-bind="visible: status() != 'ready' && status() != 'loading' && errors().length == 0, click: function() { $data.showLogs(! $data.showLogs()); window.setTimeout(redrawFixedHeaders, 100); }, css: {'blue': $data.showLogs}" title="${ _('Show Logs') }">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: status() != 'ready' && status() != 'loading' && errors().length == 0, click: function() { $data.showLogs(! $data.showLogs()); window.setTimeout(redrawFixedHeaders, 100); }, css: {'blue': $data.showLogs}" title="${ _('Show Logs') }">
         <i class="fa fa-file-text-o"></i>
       </a>
 
@@ -832,19 +832,19 @@ ${ require.config() }
         <input type="hidden" name="snippet"/>
         <input type="hidden" name="format" class="download-format"/>
 
-        <div class="hover-actions hover-dropdown" data-bind="visible: status() == 'available' && result.hasSomeResults() && result.type() == 'table'">
-          <a class="dropdown-toggle" data-toggle="dropdown">
+        <div class="hover-dropdown" data-bind="visible: status() == 'available' && result.hasSomeResults() && result.type() == 'table'" style="display:none;">
+          <a class="inactive-action dropdown-toggle pointer" data-toggle="dropdown">
             <i class="fa fa-download"></i>
             <i class="fa fa-caret-down"></i>
           </a>
           <ul class="dropdown-menu pull-right">
             <li>
-              <a class="download" href="javascript:void(0)" data-bind="click: function() { downloadResult($data, 'csv'); }" title="${ _('Download first rows as CSV') }">
+              <a class="inactive-action download" href="javascript:void(0)" data-bind="click: function() { downloadResult($data, 'csv'); }" title="${ _('Download first rows as CSV') }">
                 <i class="fa fa-file-o"></i> ${ _('CSV') }
               </a>
             </li>
             <li>
-              <a class="download" href="javascript:void(0)" data-bind="click: function() { downloadResult($data, 'xls'); }" title="${ _('Download first rows as XLS') }">
+              <a class="inactive-action download" href="javascript:void(0)" data-bind="click: function() { downloadResult($data, 'xls'); }" title="${ _('Download first rows as XLS') }">
                 <i class="fa fa-file-excel-o"></i> ${ _('Excel') }
               </a>
             </li>