← Back to team overview

yellow team mailing list archive

[Merge] lp:~tveronezi/juju-gui/service-header into lp:juju-gui

 

Thiago Veronezi has proposed merging lp:~tveronezi/juju-gui/service-header into lp:juju-gui.

Requested reviews:
  Juju GUI Hackers (juju-gui)

For more details, see:
https://code.launchpad.net/~tveronezi/juju-gui/service-header/+merge/130243

Service view header should match design doc

The service view header should match visual design document.
(https://docs.google.com/a/canonical.com/file/d/0B6l8lFdCRvtqS19SYWQ2MzU3cFU/edit)
-- 
https://code.launchpad.net/~tveronezi/juju-gui/service-header/+merge/130243
Your team Juju GUI Hackers is requested to review the proposed merge of lp:~tveronezi/juju-gui/service-header into lp:juju-gui.
=== added file 'app/assets/images/slider_off.png'
Binary files app/assets/images/slider_off.png	1970-01-01 00:00:00 +0000 and app/assets/images/slider_off.png	2012-10-17 22:10:34 +0000 differ
=== added file 'app/assets/images/slider_on.png'
Binary files app/assets/images/slider_on.png	1970-01-01 00:00:00 +0000 and app/assets/images/slider_on.png	2012-10-17 22:10:34 +0000 differ
=== added file 'app/assets/images/tab_div.png'
Binary files app/assets/images/tab_div.png	1970-01-01 00:00:00 +0000 and app/assets/images/tab_div.png	2012-10-17 22:10:34 +0000 differ
=== added file 'app/assets/images/tab_marker.png'
Binary files app/assets/images/tab_marker.png	1970-01-01 00:00:00 +0000 and app/assets/images/tab_marker.png	2012-10-17 22:10:34 +0000 differ
=== removed file 'app/assets/images/white-triangle-16X8.png'
Binary files app/assets/images/white-triangle-16X8.png	2012-10-11 18:06:58 +0000 and app/assets/images/white-triangle-16X8.png	1970-01-01 00:00:00 +0000 differ
=== added file 'app/templates/service-footer-common-controls.partial'
--- app/templates/service-footer-common-controls.partial	1970-01-01 00:00:00 +0000
+++ app/templates/service-footer-common-controls.partial	2012-10-17 22:10:34 +0000
@@ -0,0 +1,26 @@
+<div class="inline service-common-controls">
+ {{#unless charm.is_subordinate}}
+ <div class="control-unit-count">
+   <div class="inline"><span>Unit count</span></div>
+   <div class="inline">
+     <input type="text" id="num-service-units" value="{{service.unit_count}}">
+   </div>
+ </div>
+ {{/unless}}
+ <div class="control-expose">
+   <div class="inline"><span>Expose</span></div>
+   <div class="inline">
+   {{#if service.exposed}}
+     <img class="unexposeService"
+          alt="Exposed"
+          src="/juju-ui/assets/images/slider_on.png" />
+     <span class="on">On</span>
+   {{else}}
+     <img class="exposeService"
+          alt="Not exposed"
+          src="/juju-ui/assets/images/slider_off.png" />
+     <span class="off">Off</span>
+   {{/if}}
+   </div>
+  </div>
+</div>
\ No newline at end of file

=== modified file 'app/templates/service-footer.partial'
--- app/templates/service-footer.partial	2012-10-12 18:27:58 +0000
+++ app/templates/service-footer.partial	2012-10-17 22:10:34 +0000
@@ -1,8 +1,14 @@
-<div class="juju-service-info-container-bottom-menu">
-  <div class="juju-service-info-container-bottom-menu-wrapper">
-    <div class="inline">
-     <a class="btn" id="destroy-service" data-toggle="modal" 
-       href="#destroy-service-modal">Destroy Service</a>
+<div class="service-view">
+  <div class="juju-service-info-container-bottom-menu">
+    <div class="destroy-control">
+      <a id="destroy-service" data-toggle="modal" 
+         href="#destroy-service-modal">
+        <img src="/juju-ui/assets/images/destroy_icon.png" />Destroy
+      </a>
+      <img class="divider" 
+           src="/juju-ui/assets/images/bottom_bar_big_div.png" />
     </div>
+  
+    {{> service-footer-common-controls}}
   </div>
-</div> 
\ No newline at end of file
+</div>
\ No newline at end of file

=== modified file 'app/templates/service-header.partial'
--- app/templates/service-header.partial	2012-10-12 18:27:36 +0000
+++ app/templates/service-header.partial	2012-10-17 22:10:34 +0000
@@ -1,66 +1,26 @@
-<div class="service-header-partial">
-<div class="hero-unit juju-service-info-container">
-  <div class="inline juju-service-info-container-big">
-    <div class="juju-service-container-header-name">
-      <div class="juju-sevice-container-name">
-        <span><strong>{{service.id}}</strong></span>
-      </div>
-      <div>
-        <span><strong>{{charm.id}}</strong></span>
-        <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
-        <a class="juju-service-charm-link" 
-           href="{{charm.app_url}}">Show Charm</a>
-      </div>
-    </div>
-  </div>
-  <div class="inline juju-service-info-container-separator"></div>
-  <div class="inline juju-service-info-container-separator"></div>
-  <div class="inline juju-service-info-container-small">
-    <div class="juju-service-container-header-expose">
-    {{#if service.exposed}}
-      <button class="btn unexposeService">
-        <i class="icon-play-circle"></i>Unexpose
-      </button>
-    {{else}}
-      <button class="btn exposeService">
-        <i class="icon-ban-circle"></i>Expose
-      </button>
-    {{/if}}
-    </div>
-  </div>
-  <div class="inline juju-service-info-container-separator"></div>
-  <div class="inline juju-service-info-container-separator"></div>
-  <div class="inline juju-service-info-container-small">
-    <div class="juju-service-container-header-unit-count">
-    {{#unless charm.is_subordinate}}
-      <form class="unit-count-form">
-        <label class="control-label" for="num-service-units">Unit count</label>
-        <input type="text" id="num-service-units" 
-               value="{{service.unit_count}}">
-      </form>    
-    {{/unless}}
-    </div>
-  </div>  
-  
-  
-</div>
-<div class="juju-service-info-container-menu">
-  <div class="juju-service-info-container-menu-wrapper">
-    {{#tabs}}
-    <div class="inline juju-menu-item">
-      <div>
-        <a href="{{href}}"><strong>{{title}}</strong></a> 
-      </div>
-      <div>
-        {{#if active}}
-        <img src="/juju-ui/assets/images/white-triangle-16X8.png" />
-        {{/if}}
-      </div>
-    </div>
-    {{/tabs}}
-  </div>
-</div>
-<div class="row">
-  <div id="message-area" class="span10"></div>
-</div>
+<div class="service-view">
+  <div class="service-header-partial">
+    <div class="name crosshatch-background">
+      <div>
+        <span>{{service.id}}</span>
+      </div>
+      <div>
+        <span>{{charm.id}}</span>
+      </div>
+    </div>
+    <div class="menu-items">
+      {{#tabs}}
+      <div class="inline item">
+        <div>
+          <a href="{{href}}">{{title}}</a> 
+        </div>
+        <div {{#if active}}class="active"{{/if}}>
+        </div>
+      </div>
+      {{/tabs}}
+    </div>
+    <div class="row">
+      <div id="message-area" class="span10"></div>
+    </div>
+  </div>
 </div>  
\ No newline at end of file

=== modified file 'app/templates/service.handlebars'
--- app/templates/service.handlebars	2012-10-12 23:43:24 +0000
+++ app/templates/service.handlebars	2012-10-17 22:10:34 +0000
@@ -7,22 +7,24 @@
 </div>
 <div id="destroy-modal-panel"></div>
 </div>
-<div class="juju-service-info-container-bottom-menu">
-	<div>
-	  <div class="inline">
-	   <a class="btn" id="destroy-service" data-toggle="modal" 
-	      href="#destroy-service-modal">Destroy Service</a>
-	  </div>
-    <div class="inline juju-service-info-container-separator-footer"></div>
-	  <div class="inline">
-		  <div class="juju-service-info-container-bottom-menu-wrapper-service">
-	      <span class="state-filter-label"><strong>Filter</strong>&nbsp;</span>
-	      {{#states}}
-	      <a class="state-btn btn {{#if active}}btn-primary{{/if}}" 
-	        data-toggle="modal" href="{{link}}">
-	        <span class="state-title">{{title}}</span> ({{count}})</a>
-	      {{/states}}
-		  </div>
-	  </div>
-	</div>
-</div>  
\ No newline at end of file
+<div class="service-view">
+  <div class="juju-service-info-container-bottom-menu">
+    <div class="destroy-control">
+      <a id="destroy-service" data-toggle="modal" 
+         href="#destroy-service-modal">
+        <img src="/juju-ui/assets/images/destroy_icon.png" />Destroy
+      </a>
+      <img class="divider" 
+           src="/juju-ui/assets/images/bottom_bar_big_div.png" />
+    </div>
+    <div class="filter-control">
+      <span>Filter</span>
+      {{#states}}
+      <a class="state-btn btn {{#if active}}btn-primary{{/if}}" 
+        data-toggle="modal" href="{{link}}">
+        <span class="state-title">{{title}}</span> ({{count}})</a>
+      {{/states}}
+    </div>
+    {{> service-footer-common-controls}}
+  </div> 
+</div>

=== modified file 'app/views/environment.js'
--- app/views/environment.js	2012-10-15 19:22:40 +0000
+++ app/views/environment.js	2012-10-17 22:10:34 +0000
@@ -84,6 +84,21 @@
               self.set('potential_drop_point_service', d);
               self.set('potential_drop_point_rect', rect);
               self.addSVGClass(rect, 'hover');
+
+              // If we have an active dragline, stop redrawing it on mousemove
+              // and draw the line between the two nearest connector points of
+              // the two services.
+              if (self.dragline) {
+                var connectors = d.getConnectorPair(
+                    self.get('addRelationStart_service')),
+                    s = connectors[0],
+                    t = connectors[1];
+                self.dragline.attr('x1', t[0])
+                  .attr('y1', t[1])
+                  .attr('x2', s[0])
+                  .attr('y2', s[1])
+                  .attr('class', 'relation pending-relation dragline');
+              }
             },
             mouseleave: function(d, self) {
               // Do not fire if we aren't looking for a relation endpoint.
@@ -101,6 +116,11 @@
               self.set('potential_drop_point_service', null);
               self.set('potential_drop_point_rect', null);
               self.removeSVGClass(rect, 'hover');
+
+              if (self.dragline) {
+                self.dragline.attr('class',
+                    'relation pending-relation dragline dragging');
+              }
             }
           },
           '.sub-rel-block': {
@@ -254,6 +274,8 @@
             .attr('height', height)
             .append('svg:g')
             .call(zoom)
+              // Disable zoom on double click.
+            .on('dblclick.zoom', null)
             .append('g');
 
           vis.append('svg:rect')
@@ -386,7 +408,7 @@
         },
 
         /*
-         * Sync view models with curent db.models.
+         * Sync view models with current db.models.
          */
         updateData: function() {
           //model data
@@ -775,7 +797,13 @@
               });
 
           var status_chart_layout = d3.layout.pie()
-            .value(function(d) { return (d.value ? d.value : 1); });
+            .value(function(d) { return (d.value ? d.value : 1); })
+            .sort(function(a, b) {
+                // Ensure that the service health graphs will be renders in
+                // the correct order: error - pending - running.
+                var states = {error: 0, pending: 1, running: 2};
+                return states[a.name] - states[b.name];
+              });
 
           // Append to status charts to non-subordinate services
           var status_chart = node.append('g')
@@ -872,14 +900,17 @@
           });
         },
         renderSlider: function() {
-          var self = this;
+          var self = this,
+              value = 100,
+              currentScale = this.get('scale');
           // Build a slider to control zoom level
-          // TODO once we have a stored value in view models, use that
-          // for the value property, but for now, zoom to 100%
+          if (currentScale) {
+            value = currentScale * 100;
+          }
           var slider = new Y.Slider({
             min: 25,
             max: 200,
-            value: 100
+            value: value
           });
           slider.render('#slider-parent');
           slider.after('valueChange', function(evt) {
@@ -949,6 +980,22 @@
               .setAttribute('x', -width / 2);
           });
 
+          // Preserve zoom when the scene is updated.
+          var changed = false,
+              currentScale = this.get('scale'),
+              currentTranslate = this.get('translate');
+          if (currentTranslate && currentTranslate !== this.zoom.translate()) {
+            this.zoom.translate(currentTranslate);
+            changed = true;
+          }
+          if (currentScale && currentScale !== this.zoom.scale()) {
+            this.zoom.scale(currentScale);
+            changed = true;
+          }
+          if (changed) {
+            this._fire_zoom(0);
+          }
+
           // Render the slider after the view is attached.
           // Although there is a .syncUI() method on sliders, it does not
           // seem to play well with the app framework: the slider will render
@@ -977,7 +1024,7 @@
         addRelationDragStart: function(d, context) {
           // Create a pending drag-line behind services.
           var dragline = this.vis.insert('line', '.service')
-              .attr('class', 'relation pending-relation dragline'),
+              .attr('class', 'relation pending-relation dragline dragging'),
               self = this;
 
           // Start the line in the middle of the service.
@@ -987,6 +1034,7 @@
               .attr('x2', point[0])
               .attr('y2', point[1]);
           self.dragline = dragline;
+          self.cursorBox = views.BoundingBox();
 
           // Start the add-relation process.
           self.service_click_actions
@@ -994,9 +1042,21 @@
         },
 
         addRelationDrag: function(d, context) {
-          // Rubberband our potential relation line.
-          this.dragline.attr('x2', d3.event.x)
+          // Rubberband our potential relation line if we're not currently
+          // hovering over a potential drop-point.
+          if (!this.get('potential_drop_point_service')) {
+            // Create a BoundingBox for our cursor.
+            this.cursorBox.pos = {x: d3.event.x, y: d3.event.y, w: 0, h: 0};
+
+            // Draw the relation line from the connector point nearest the
+            // cursor to the cursor itself.
+            var connectors = this.cursorBox.getConnectorPair(d),
+                s = connectors[1];
+            this.dragline.attr('x1', s[0])
+              .attr('y1', s[1])
+              .attr('x2', d3.event.x)
               .attr('y2', d3.event.y);
+          }
         },
 
         addRelationDragEnd: function(d, context) {
@@ -1006,8 +1066,9 @@
           var endpoint = self.get('potential_drop_point_service');
 
           // Get rid of our drag line
-          this.dragline.remove();
-          this.buildingRelation = false;
+          self.dragline.remove();
+          self.buildingRelation = false;
+          self.cursorBox = null;
 
           // If we landed on a rect, add relation, otherwise, cancel.
           if (rect) {
@@ -1133,7 +1194,11 @@
           if (new_scale < 25 || new_scale > 200) {
             evt.scale = this.get('scale');
           }
+          // Store the current value of scale so that it can be restored later.
           this.set('scale', evt.scale);
+          // Store the current value of translate as well, by copying the event
+          // array in order to avoid reference sharing.
+          this.set('translate', evt.translate.slice(0));
           vis.attr('transform', 'translate(' + evt.translate + ')' +
               ' scale(' + evt.scale + ')');
           this.updateServiceMenuLocation();

=== modified file 'app/views/service.js'
--- app/views/service.js	2012-10-14 00:51:02 +0000
+++ app/views/service.js	2012-10-17 22:10:34 +0000
@@ -223,8 +223,8 @@
 
   var exposeButtonMixin = {
     events: {
-      '.unexposeService': {click: 'unexposeService'},
-      '.exposeService': {click: 'exposeService'}
+      '.unexposeService': {mousedown: 'unexposeService'},
+      '.exposeService': {mousedown: 'exposeService'}
     },
 
     unexposeService: function() {
@@ -297,7 +297,7 @@
           }, this));
         },
 
-        getServiceTabs: function(href) {
+        getServiceTabs: function(href, charm) {
           var tabs = [{
             href: '.',
             title: 'Units',
@@ -311,6 +311,10 @@
             title: 'Settings',
             active: false
           }, {
+            href: (charm ? charm.app_url : '#'),
+            title: 'Charm',
+            active: false
+          }, {
             href: 'constraints',
             title: 'Constraints',
             active: false
@@ -379,7 +383,8 @@
 
           container.setHTML(this.template(
               { viewName: 'relations',
-                tabs: this.getServiceTabs('relations'),
+                tabs: this.getServiceTabs('relations',
+                    this.renderable_charm(service.get('charm'), app)),
                 service: service.getAttrs(),
                 relations: relation_data,
                 charm: this.renderable_charm(service.get('charm'), app)}
@@ -560,7 +565,8 @@
           console.log('service constraints', display_constraints);
           container.setHTML(this.template({
             viewName: 'constraints',
-            tabs: this.getServiceTabs('constraints'),
+            tabs: this.getServiceTabs('constraints',
+                this.renderable_charm(service.get('charm'), app)),
             service: service.getAttrs(),
             constraints: display_constraints,
             readOnlyConstraints: (function() {
@@ -638,7 +644,8 @@
 
           container.setHTML(this.template(
               { viewName: 'config',
-                tabs: this.getServiceTabs('config'),
+                tabs: this.getServiceTabs('config',
+                    this.renderable_charm(service.get('charm'), app)),
                 service: service.getAttrs(),
                 settings: settings,
                 charm: this.renderable_charm(service.get('charm'), app)}
@@ -809,7 +816,8 @@
           }, this);
           container.setHTML(this.template({
             viewName: 'units',
-            tabs: this.getServiceTabs('.'),
+            tabs: this.getServiceTabs('.',
+                this.renderable_charm(service.get('charm'), app)),
             service: service.getAttrs(),
             charm: this.renderable_charm(service.get('charm'), app),
             state: filter_state,

=== modified file 'lib/views/stylesheet.less'
--- lib/views/stylesheet.less	2012-10-15 23:49:23 +0000
+++ lib/views/stylesheet.less	2012-10-17 22:10:34 +0000
@@ -234,6 +234,10 @@
 
     &.pending-relation {
         stroke: #faaf40;
+        
+        &.dragging {
+            stroke: #fa6a40
+        }
     }
 
     &.unused {
@@ -830,136 +834,142 @@
    background: pink;
 }
 
-.juju-service-info-container {
-  padding: 0px;
-  margin-bottom: 0px;
-  background-color: #515151;
-  color: white;
-  height: 70px;
-  -webkit-border-radius: 0px;
-  -moz-border-radius: 0px;
-  border-radius: 0px;
-  -webkit-border-top-left-radius: 6px;
-  -moz-border-top-left-radius: 6px;
-  border-top-left-radius: 6px;
-  -webkit-border-top-right-radius: 6px;
-  -moz-border-top-right-radius: 6px;
-  border-top-right-radius: 6px;
-
-  .juju-service-info-container-big {
-    width: 58%;
-
-    .juju-service-container-header-name {
-      padding-top: 15px;
-      padding-left: 10px;
-
-      .juju-sevice-container-name {
-        padding-bottom: 5px;
-
-        span {
-          font-size: 3em;
-        }
-      }
-    }
-  }
-
-  .juju-service-info-container-small {
-    width: 20%;
-
-    .juju-service-container-header-expose {
-      padding: 20px;
-    }
-
-    .juju-service-container-header-unit-count {
-      padding: 5px;
-
-      input {
-        width: 95%
-      }
-    }
-  }
-}
-
-.juju-service-info-container-menu {
-  border-top: 1px solid #737373;
-  background-color: black;
-  height: 40px;
-  text-align: center;
-}
-
-.juju-service-info-container-menu-wrapper {
-  display: inline-block;
-}
-
-.juju-menu-item {
-  color: white;
-  padding: 10px;
-  padding-bottom: 0px;
-  padding-top: 10px;
-  text-align: center;
-
-  a {
-    color: white;
-    text-decoration: none;
-  }
-
-  a:HOVER {
-    color: white;
-    text-decoration: none;
-  }
-}
-
-.unit-count-form {
-  margin-bottom: 0px;
-}
-
-.juju-service-charm-link,.juju-service-charm-link:HOVER {
-  color: #919191;
-  text-decoration: underline;
-}
-
-.juju-service-info-container-separator {
-  width: 0px;
-  height: 68px;
-  border: 1px solid #737373;
-  margin-left: 1px;
-}
-
-.juju-service-info-container-separator-footer {
-  height: 48px;
-}
-
-.juju-service-info-container-bottom-menu {
-  border-top: 1px solid #737373;
-  background-color: black;
-  height: 50px;
-
-  .juju-service-info-container-bottom-menu-wrapper {
-    margin-left: 10px;
-
-    div {
-      width: 200px
-    }
-  }
-
-  .juju-service-info-container-bottom-menu-wrapper-service {
-    margin-top: 10px;
-    margin-left: 10px;
-
-    div {
-      width: 200px
-    }
-  }
-
-  #destroy-service {
+#destroy-service {
     margin-top: 10px;
     margin-left: 10px;
     margin-right: 10px;
-  }
 }
 
-.state-filter-label {
-  color: white
+.service-view {
+  .juju-service-info-container-bottom-menu {
+    background: url(/juju-ui/assets/images/bottom_bar.png) repeat-x;
+    height: 50px;
+    position: relative;
+    color: #2d2d2d!important;
+
+    .destroy-control {
+      position: absolute;
+      top: 1px;
+      left: 10px;
+
+      a {
+        color: #2D2D2D!important;
+      }
+      
+      a:link, a:visited, a:active, a:hover {
+        text-decoration: none
+      }
+    }
+  }
+
+  .filter-control {
+    position: absolute;
+    left: 120px;
+    top: 10px;
+    
+    span:first-child {
+      padding-right: 5px;
+    }
+    
+  }
+
+.service-header-partial {
+    .name {
+        padding-top: 38px;
+        padding-left: 46px;
+        height: 111px;
+
+        div:nth-child(1) {
+          font-family: @font-family;
+          font-style: regular;
+          font-size: 22px; fill: #292929;
+        }
+
+        div:nth-child(2) {
+          padding-top: 18px;
+          font-family: @font-family;
+          font-style: italic;
+          font-size: 16px; fill: #6a737b;
+        }
+    }
+
+    .menu-items {
+      height: 26px;
+      padding-top: 2px;
+      padding-left: 38px;
+      background: url(/juju-ui/assets/images/tab_div.png) repeat;
+
+      .item {
+        a {
+          font-family: @font-family;
+          font-style: medium;
+          font-size: 12px; fill: #dd4814;
+          padding-left: 10px;
+          padding-right: 10px;
+          color: #2D2D2D!important;
+        }
+        a:link, a:visited, a:active, a:hover {
+          text-decoration: none
+        }
+        a:first-child {
+          color: #2D2D2D!important;
+        }
+        .active {
+          margin-top: 3px;
+          height: 4px;
+          background: url(/juju-ui/assets/images/tab_marker.png) repeat;
+        }
+      }
+    }
+  }
+
+  .service-common-controls {
+    position: absolute;
+    right: 10px;
+    top: 10px;
+
+    .control-unit-count {
+        position: absolute;
+        right: 130px;
+        top: 0px;
+        width: 290px;
+        
+        div {
+          padding-left: 5px;
+        }
+        
+        div:first-child {
+          padding-top: 4px;
+        }
+    }
+    
+    .control-expose {
+        position: absolute;
+        right: 0px;
+        top: 4px;
+        width: 120px;
+        
+        div {
+          padding-left: 5px;
+        }
+
+        .on {
+          font-family: @font-family;
+          font-size: 12px; fill: #ffffff;
+          position: absolute; 
+          right: 20px;
+          top: 3px;
+        }
+    
+        .off {
+          font-family: @font-family;
+          font-size: 12px; fill: #292929;
+          position: absolute; 
+          right: 40px;
+          top: 3px;
+        }
+      }
+  }
 }
 
 div.inline {


Follow ups