[Bps-public-commit] rt-extension-lifecycleui branch, master, updated. 7a6bc22674729587b806aff3d26f7064df62f216

Shawn Moore shawn at bestpractical.com
Thu Sep 28 15:12:30 EDT 2017


The branch, master has been updated
       via  7a6bc22674729587b806aff3d26f7064df62f216 (commit)
       via  df52f6a9213e012c56ea044154f62b0946240007 (commit)
       via  d9c232bebc139f15a86e9bb3342aa2dbc8cb5526 (commit)
       via  4d2b6423de1c42110c65185a92be37684c912351 (commit)
       via  1bdf8b4cfe91640e0546179f20cbd080467e1337 (commit)
       via  f57430221b615a79a7d9d4079901837d0c98665b (commit)
       via  95b6d2774298ce230f43db7a371bae2025dd3229 (commit)
       via  28306559e74bbb32d91966f949756a0b1a83fd8e (commit)
       via  763aae80dabbe5449b5b7e4834d7475fbc72df35 (commit)
       via  08db1c7eac5a8d5edb0f5c85d045640e277e8ba4 (commit)
       via  32f6576d821fd10710cfabab52a01751be2e578d (commit)
       via  af0dcd5f5e7016d95185ce6a20b35c550fbf6022 (commit)
       via  a6f76cbf27abcc77a86dc6bcc11fa5b0573204f1 (commit)
       via  7c0cc10d21fde2308f5e48bd122d5d60afcd8860 (commit)
      from  7d5755a6787e7fa8b9dd741982a08846bc4f6de4 (commit)

Summary of changes:
 Changes                                            |  23 +++++
 MANIFEST                                           |   2 +
 META.yml                                           |   2 +-
 .../Ticket/Elements/ShowSummary/AfterReminders     |  13 ++-
 html/Elements/LifecycleGraph                       |  16 +++-
 html/Elements/LifecycleInspector                   |  10 +-
 html/Elements/LifecycleInspectorCanvas             |  81 ++++++----------
 html/Elements/LifecycleInspectorHeader             |  65 +++++++++++++
 html/Elements/LifecycleInspectorStatus             |   2 +-
 lib/RT/Extension/LifecycleUI.pm                    |   2 +-
 static/css/lifecycleui-editor.css                  |  19 +++-
 static/css/lifecycleui-viewer.css                  |  12 +++
 static/js/lifecycleui-editor.js                    |  53 ++++++++---
 static/js/lifecycleui-model.js                     | 106 ++++++++++++++-------
 static/js/lifecycleui-viewer-interactive.js        |   8 +-
 static/js/lifecycleui-viewer.js                    |  34 ++++++-
 16 files changed, 322 insertions(+), 126 deletions(-)
 create mode 100644 Changes
 create mode 100644 html/Elements/LifecycleInspectorHeader

- Log -----------------------------------------------------------------
commit 7c0cc10d21fde2308f5e48bd122d5d60afcd8860
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:11:56 2017 +0000

    Fix zoom to fit for ticket display
    
    Need to not factor in the initial scale and instead base the transform
    off d3.zoomIdentity

diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index b711b1d..17e299f 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -73,11 +73,11 @@ jQuery(function () {
             height = bounds.height;
         var midX = bounds.x + width / 2,
             midY = bounds.y + height / 2;
-        var scale = 0.75 / Math.max(width / fullWidth, height / fullHeight);
+        var scale = .9 / Math.max(width / fullWidth, height / fullHeight);
         var tx = fullWidth / 2 - scale * midX;
         var ty = fullHeight / 2 - scale * midY;
 
-        this._setZoom(this._zoomIdentity.translate(tx, ty).scale(scale), animated);
+        this._setZoom(d3.zoomIdentity.translate(tx, ty).scale(scale), animated);
     };
 
     Viewer.prototype.didEnterStatusNodes = function (statuses) { };

commit a6f76cbf27abcc77a86dc6bcc11fa5b0573204f1
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:24:32 2017 +0000

    Add zoom and center options to lifecycles

diff --git a/html/Elements/LifecycleInspectorCanvas b/html/Elements/LifecycleInspectorCanvas
index bd2a9a9..e8dd870 100644
--- a/html/Elements/LifecycleInspectorCanvas
+++ b/html/Elements/LifecycleInspectorCanvas
@@ -13,7 +13,10 @@
 
           {{#if lifecycle.is_ticket}}
           <tr>
-            <td><&|/l&>Lifecycle on Tickets</&>:</td>
+            <td colspan=2><&|/l&>Lifecycle on Tickets</&>:</td>
+          </tr>
+          <tr>
+            <td><&|/l&>Display</&>:</td>
             <td><select name="ticket_display">
               {{#select lifecycle.ticket_display}}
                 <option value="hidden"><&|/l&>hidden</&>
@@ -22,6 +25,25 @@
               {{/select}}
             </select></td>
           </tr>
+          <tr>
+            <td><&|/l&>Pan and Zoom</&>:</td>
+            <td><select name="ticket_zoom">
+              {{#select lifecycle.ticket_zoom}}
+                <option value="dynamic"><&|/l&>adjustable</&>
+                <option value="static"><&|/l&>static</&>
+              {{/select}}
+            </select></td>
+          </tr>
+          <tr>
+            <td><&|/l&>Center Point</&>:</td>
+            <td><select name="ticket_center">
+              {{#select lifecycle.ticket_center}}
+                <option value="status"><&|/l&>current status</&>
+                <option value="origin"><&|/l&>origin</&>
+                <option value="fit"><&|/l&>zoom to fit</&>
+              {{/select}}
+            </select></td>
+          </tr>
           {{/if}}
 
           <tr>
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 22bc8b3..4882ba0 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -9,6 +9,8 @@ jQuery(function () {
         this.defaults = {};
         this.transitions = [];
         this.decorations = {};
+        this.ticket_zoom = 'dynamic';
+        this.ticket_center = 'status';
 
         this.defaultColor = '#547CCC';
         this._undoState = { undoStack: [], redoStack: [] };
@@ -46,6 +48,14 @@ jQuery(function () {
             self.ticket_display = config.ticket_display;
         }
 
+        if (config.ticket_zoom) {
+            self.ticket_zoom = config.ticket_zoom;
+        }
+
+        if (config.ticket_center) {
+            self.ticket_center = config.ticket_center;
+        }
+
         jQuery.each(['initial', 'active', 'inactive'], function (i, type) {
             if (config[type]) {
                 self.statuses = self.statuses.concat(config[type]);
@@ -252,6 +262,8 @@ jQuery(function () {
             transitions: self.transitions,
 
             ticket_display: self.ticket_display,
+            ticket_zoom: self.ticket_zoom,
+            ticket_center: self.ticket_center,
             decorations: {},
             statusExtra: {},
             transitionExtra: {}
@@ -665,7 +677,7 @@ jQuery(function () {
         if (field == 'on_create' || field == 'approved' || field == 'denied' || field == 'reminder_on_open' || field == 'reminder_on_resolve') {
             this.defaults[field] = value;
         }
-        else if (field == 'ticket_display') {
+        else if (field == 'ticket_display' || field == 'ticket_zoom' || field == 'ticket_center') {
             this[field] = value;
         }
         else {

commit af0dcd5f5e7016d95185ce6a20b35c550fbf6022
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:33:51 2017 +0000

    Implement the three center options

diff --git a/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders b/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
index 88ac753..6d4b830 100644
--- a/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
+++ b/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
@@ -2,8 +2,12 @@
 $Ticket
 </%ARGS>
 <%INIT>
-my $config = $Ticket->LifecycleObj->{data}{ticket_display} || 'hidden';
-return unless $config eq 'readonly' || $config eq 'interactive';
+my $config = $Ticket->LifecycleObj->{data};
+
+my $display_config = $config->{ticket_display} || 'hidden';
+return unless $display_config eq 'readonly' || $display_config eq 'interactive';
+
+my $center_config = $config->{ticket_center} || 'origin';
 </%INIT>
 <&| /Widgets/TitleBox,
     title => loc("Lifecycle"),
@@ -11,6 +15,7 @@ return unless $config eq 'readonly' || $config eq 'interactive';
 &>
     <& /Elements/LifecycleGraph,
            Ticket => $Ticket,
-           Interactive => ($config eq 'interactive'),
+           Interactive => ($display_config eq 'interactive'),
+           Center => $center_config,
     &>
 </&>
diff --git a/html/Elements/LifecycleGraph b/html/Elements/LifecycleGraph
index 5c291f0..0f2628a 100644
--- a/html/Elements/LifecycleGraph
+++ b/html/Elements/LifecycleGraph
@@ -1,4 +1,4 @@
-<div class="lifecycle-ui<% $Editing ? ' editing' : '' %><% $Interactive ? ' interactive' : '' %>" id="lifecycle-<% $id %>">
+<div class="lifecycle-ui<% $Editing ? ' editing' : '' %><% $Interactive ? ' interactive' : '' %> center-<% $Center || 'origin' %>" id="lifecycle-<% $id %>">
     <div class="lifecycle-view">
       <div class="overlay-buttons">
           <button class="zoom-in">+</button>
@@ -52,6 +52,7 @@
 <%ARGS>
 $Editing => 0
 $Interactive => 0
+$Center => 'origin'
 $Lifecycle => undef
 $Ticket => undef
 </%ARGS>
@@ -63,4 +64,9 @@ my $config = RT->Config->Get('Lifecycles')->{$Lifecycle};
 Abort("Invalid Lifecycle") if !$Lifecycle || !$config;
 
 my $id = $Lifecycle . '-' . int(rand(2**31));
+
+if ($Editing) {
+    $Center = 'origin';
+}
+
 </%INIT>
diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index 17e299f..d0d8c02 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -536,9 +536,23 @@ jQuery(function () {
 
         self.addZoomBehavior();
 
-        self.focusOnStatus(focusStatus, true, false);
+        if (self.container.hasClass('center-status')) {
+            self.focusOnStatus(focusStatus, true, false);
+            self.renderDisplay(true);
+        }
+        else {
+            self.focusOnStatus(focusStatus, false, false);
+            self.renderDisplay(true);
+
+            if (self.container.hasClass('center-fit')) {
+                self.zoomToFit(false);
+            }
+            else if (self.container.hasClass('center-origin')) {
+                self.resetZoom(false);
+            }
+        }
 
-        self.renderDisplay(true);
+        self._zoomIdentity = self._currentZoom;
 
         self.container.on('click', 'button.zoom-in', function (e) {
             e.preventDefault();

commit 32f6576d821fd10710cfabab52a01751be2e578d
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:46:06 2017 +0000

    Implement zoom being optional

diff --git a/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders b/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
index 6d4b830..461ba03 100644
--- a/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
+++ b/html/Callbacks/RT-Extension-LifecycleUI/Ticket/Elements/ShowSummary/AfterReminders
@@ -7,6 +7,7 @@ my $config = $Ticket->LifecycleObj->{data};
 my $display_config = $config->{ticket_display} || 'hidden';
 return unless $display_config eq 'readonly' || $display_config eq 'interactive';
 
+my $zoom_config = $config->{ticket_zoom} || 'static';
 my $center_config = $config->{ticket_center} || 'origin';
 </%INIT>
 <&| /Widgets/TitleBox,
@@ -16,6 +17,7 @@ my $center_config = $config->{ticket_center} || 'origin';
     <& /Elements/LifecycleGraph,
            Ticket => $Ticket,
            Interactive => ($display_config eq 'interactive'),
+           Zoomable => ($zoom_config eq 'dynamic'),
            Center => $center_config,
     &>
 </&>
diff --git a/html/Elements/LifecycleGraph b/html/Elements/LifecycleGraph
index 0f2628a..787840b 100644
--- a/html/Elements/LifecycleGraph
+++ b/html/Elements/LifecycleGraph
@@ -1,9 +1,9 @@
-<div class="lifecycle-ui<% $Editing ? ' editing' : '' %><% $Interactive ? ' interactive' : '' %> center-<% $Center || 'origin' %>" id="lifecycle-<% $id %>">
+<div class="lifecycle-ui<% $Editing ? ' editing' : '' %><% $Interactive ? ' interactive' : '' %><% $Zoomable ? ' zoomable' : '' %> center-<% $Center || 'origin' %>" id="lifecycle-<% $id %>">
     <div class="lifecycle-view">
       <div class="overlay-buttons">
-          <button class="zoom-in">+</button>
-          <button class="zoom-reset">0</button>
-          <button class="zoom-out">-</button>
+          <button class="zoom zoom-in">+</button>
+          <button class="zoom zoom-reset">0</button>
+          <button class="zoom zoom-out">-</button>
       </div>
       <svg>
           <& /Elements/LifecycleGraphExtras, %ARGS &>
@@ -52,6 +52,7 @@
 <%ARGS>
 $Editing => 0
 $Interactive => 0
+$Zoomable => 0
 $Center => 'origin'
 $Lifecycle => undef
 $Ticket => undef
@@ -66,6 +67,7 @@ Abort("Invalid Lifecycle") if !$Lifecycle || !$config;
 my $id = $Lifecycle . '-' . int(rand(2**31));
 
 if ($Editing) {
+    $Zoomable = 1;
     $Center = 'origin';
 }
 
diff --git a/static/css/lifecycleui-viewer.css b/static/css/lifecycleui-viewer.css
index 9a621e5..6e598d0 100644
--- a/static/css/lifecycleui-viewer.css
+++ b/static/css/lifecycleui-viewer.css
@@ -10,6 +10,14 @@
     left: 391px;
 }
 
+.lifecycle-ui .overlay-buttons .zoom {
+    display: none;
+}
+
+.lifecycle-ui.zoomable .overlay-buttons .zoom {
+    display: inline-block;
+}
+
 .lifecycle-ui .statuses circle {
     stroke: black;
     stroke-width: 2px;
diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index d0d8c02..85a1906 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -29,7 +29,12 @@ jQuery(function () {
         var self = this;
         self._zoom = d3.zoom()
                        .scaleExtent([.3, 2])
-                       .on("zoom", function () { self.didZoom() });
+                       .on("zoom", function () {
+                           if (self.zoomControl) {
+                               self.didZoom();
+                           }
+                       });
+
         self.svg.call(self._zoom);
     };
 
@@ -534,6 +539,9 @@ jQuery(function () {
         self.lifecycle = new RT.Lifecycle(name);
         self.lifecycle.initializeFromConfig(config);
 
+        // need to start with zoom control on to set the initial zoom
+        this.zoomControl = true;
+
         self.addZoomBehavior();
 
         if (self.container.hasClass('center-status')) {
@@ -554,6 +562,8 @@ jQuery(function () {
 
         self._zoomIdentity = self._currentZoom;
 
+        self.zoomControl = self.container.hasClass('zoomable');
+
         self.container.on('click', 'button.zoom-in', function (e) {
             e.preventDefault();
             self.zoomScale(1.25, true);

commit 08db1c7eac5a8d5edb0f5c85d045640e277e8ba4
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:49:39 2017 +0000

    Hide border on zoom to fit AND not-zoomable
    
    With this configuration, there is zero chance of any content being
    clipped off, so we can remove the border. With any other configuration,
    I feel we need to indicate the boundary (beyond which there may be more
    content) somehow to the user.

diff --git a/static/css/lifecycleui-viewer.css b/static/css/lifecycleui-viewer.css
index 6e598d0..d9805e0 100644
--- a/static/css/lifecycleui-viewer.css
+++ b/static/css/lifecycleui-viewer.css
@@ -4,6 +4,10 @@
     height: 309px;
 }
 
+.lifecycle-ui.center-fit:not(.zoomable) svg {
+    border: none;
+}
+
 .lifecycle-ui .overlay-buttons {
     position: absolute;
     top: 1em;

commit 763aae80dabbe5449b5b7e4834d7475fbc72df35
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 16:51:57 2017 +0000

    Fix interactive viewer for 1357eed change

diff --git a/static/js/lifecycleui-viewer-interactive.js b/static/js/lifecycleui-viewer-interactive.js
index df5c1bd..3292e04 100644
--- a/static/js/lifecycleui-viewer-interactive.js
+++ b/static/js/lifecycleui-viewer-interactive.js
@@ -20,8 +20,8 @@ jQuery(function () {
         }
 
         var d = this.selectedStatus;
-        var circle = this.statusContainer.select('circle[data-key="'+ d._key + '"]');
-        var bbox = circle.node().getBoundingClientRect();
+        var statusNode = this.statusContainer.select('g[data-key="'+ d._key + '"]');
+        var bbox = statusNode.node().getBoundingClientRect();
         var x = bbox.right + window.scrollX;
         var y = bbox.top + window.scrollY;
 
@@ -32,10 +32,10 @@ jQuery(function () {
         var statusName = d.name;
         this.selectedMenu = this.menuContainer.find('.status-menu[data-status="'+statusName+'"]');
         this.selectedStatus = d;
-        var circle = this.statusContainer.select('circle[data-key="'+ d._key + '"]');
+        var statusNode = this.statusContainer.select('g[data-key="'+ d._key + '"]');
 
         this.statusContainer.selectAll('.selected').classed('selected', false);
-        circle.classed('selected', true);
+        statusNode.classed('selected', true);
 
         this.menuContainer.find('.status-menu.selected').removeClass('selected');
         this.selectedMenu.addClass('selected');

commit 28306559e74bbb32d91966f949756a0b1a83fd8e
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 17:11:53 2017 +0000

    Hide pan/zoom and center controls if lifecycle is hidden

diff --git a/html/Elements/LifecycleInspectorCanvas b/html/Elements/LifecycleInspectorCanvas
index e8dd870..5d2071b 100644
--- a/html/Elements/LifecycleInspectorCanvas
+++ b/html/Elements/LifecycleInspectorCanvas
@@ -19,13 +19,13 @@
             <td><&|/l&>Display</&>:</td>
             <td><select name="ticket_display">
               {{#select lifecycle.ticket_display}}
-                <option value="hidden"><&|/l&>hidden</&>
+                <option value="hidden" data-show-hide="tr.display-option" data-show-hide-flip=1><&|/l&>hidden</&>
                 <option value="readonly"><&|/l&>read-only</&>
                 <option value="interactive"><&|/l&>interactive</&>
               {{/select}}
             </select></td>
           </tr>
-          <tr>
+          <tr class="display-option">
             <td><&|/l&>Pan and Zoom</&>:</td>
             <td><select name="ticket_zoom">
               {{#select lifecycle.ticket_zoom}}
@@ -34,7 +34,7 @@
               {{/select}}
             </select></td>
           </tr>
-          <tr>
+          <tr class="display-option">
             <td><&|/l&>Center Point</&>:</td>
             <td><select name="ticket_center">
               {{#select lifecycle.ticket_center}}
diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index 601b4fd..69a49aa 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -67,8 +67,27 @@ jQuery(function () {
         inspector.find(':checkbox[data-show-hide]').each(function () {
             var field = jQuery(this);
             var selector = field.data('show-hide');
+            var flip = field.data('show-hide-flip') ? true : false;
+
+            var toggle = function () {
+                if ((field.prop('checked') ? true : false) != flip) {
+                    jQuery(selector).show();
+                } else {
+                    jQuery(selector).hide();
+                }
+            }
+            field.change(function (e) { toggle() });
+            toggle();
+        });
+
+        inspector.find('option[data-show-hide]').each(function () {
+            var option = jQuery(this);
+            var field = option.closest('select');
+            var selector = option.data('show-hide');
+            var flip = option.data('show-hide-flip') ? true : false;
+
             var toggle = function () {
-                if (field.prop('checked')) {
+                if ((field.val() == option.val()) != flip) {
                     jQuery(selector).show();
                 } else {
                     jQuery(selector).hide();

commit 95b6d2774298ce230f43db7a371bae2025dd3229
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 17:27:16 2017 +0000

    Render inspector header dynamically
    
    This means we can untangle the HTML from the JS a bit more -- now the JS
    doesn't need to know that the header contains undo/redo buttons that
    need updating.

diff --git a/html/Elements/LifecycleInspector b/html/Elements/LifecycleInspector
index 12d4b32..57a2607 100644
--- a/html/Elements/LifecycleInspector
+++ b/html/Elements/LifecycleInspector
@@ -1,10 +1,6 @@
 <div class="inspector">
-  <div class="controls">
-    <button class="undo"><&|/l&>Undo</&></button>
-    <button class="redo"><&|/l&>Redo</&></button>
-  </div>
-  <div class="content">
-  </div>
+  <div class="header"></div>
+  <div class="content"></div>
 </div>
 
 <& LifecycleInspectorCanvas, %ARGS &>
@@ -15,3 +11,5 @@
 <& LifecycleInspectorCircle, %ARGS &>
 <& LifecycleInspectorLine, %ARGS &>
 <& LifecycleInspectorAction, %ARGS &>
+
+<& LifecycleInspectorHeader, %ARGS &>
diff --git a/html/Elements/LifecycleInspectorHeader b/html/Elements/LifecycleInspectorHeader
new file mode 100644
index 0000000..2089bb5
--- /dev/null
+++ b/html/Elements/LifecycleInspectorHeader
@@ -0,0 +1,6 @@
+<script type="text/x-template" class="lifecycle-inspector-template" data-type="header">
+  <div class="controls">
+    <button class="undo {{#unless lifecycle._canUndo}}invisible{{/unless}}"><&|/l&>Undo</&></button>
+    <button class="redo {{#unless lifecycle._canRedo}}invisible{{/unless}}"><&|/l&>Redo</&></button>
+  </div>
+</script>
diff --git a/static/css/lifecycleui-editor.css b/static/css/lifecycleui-editor.css
index 4cf2c54..799a99a 100644
--- a/static/css/lifecycleui-editor.css
+++ b/static/css/lifecycleui-editor.css
@@ -28,7 +28,7 @@
     padding: 1em;
 }
 
-.lifecycle-ui .inspector .controls {
+.lifecycle-ui .inspector .header {
     padding: 1em;
     background: #EEE;
 }
diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index 69a49aa..e004a24 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -49,22 +49,28 @@ jQuery(function () {
         return templates;
     };
 
-    Editor.prototype.setInspectorContent = function (node) {
+    Editor.prototype._refreshInspector = function (refreshContent) {
         var self = this;
         var lifecycle = self.lifecycle;
         var inspector = self.inspector;
-        self.inspectorNode = node;
-
-        var type = node ? node._type : 'canvas';
+        var node = self.inspectorNode;
 
         var params = { lifecycle: lifecycle };
-        params[type] = node;
 
-        inspector.find('.content').html(self.templates[type](params));
+        var header = inspector.find('.header');
+        header.html(self.templates.header(params));
+
+        var refreshedNode = header;
+        if (refreshContent) {
+            var type = node ? node._type : 'canvas';
+            params[type] = node;
+            inspector.find('.content').html(self.templates[type](params));
+            refreshedNode = inspector;
+        }
 
-        inspector.find(".toplevel").addClass('sf-menu sf-vertical sf-js-enabled sf-shadow').supersubs().superfish({ speed: 'fast' });
+        refreshedNode.find(".toplevel").addClass('sf-menu sf-vertical sf-js-enabled sf-shadow').supersubs().superfish({ speed: 'fast' });
 
-        inspector.find(':checkbox[data-show-hide]').each(function () {
+        refreshedNode.find(':checkbox[data-show-hide]').each(function () {
             var field = jQuery(this);
             var selector = field.data('show-hide');
             var flip = field.data('show-hide-flip') ? true : false;
@@ -80,7 +86,7 @@ jQuery(function () {
             toggle();
         });
 
-        inspector.find('option[data-show-hide]').each(function () {
+        refreshedNode.find('option[data-show-hide]').each(function () {
             var option = jQuery(this);
             var field = option.closest('select');
             var selector = option.data('show-hide');
@@ -97,11 +103,16 @@ jQuery(function () {
             toggle();
         });
 
-        inspector.find(".combobox input.combo-text").each(function () {
+        refreshedNode.find(".combobox input.combo-text").each(function () {
             ComboBox_Load(this.id);
         });
     };
 
+    Editor.prototype.setInspectorContent = function (node) {
+        this.inspectorNode = node;
+        this._refreshInspector(true);
+    };
+
     Editor.prototype.bindInspectorEvents = function () {
         var self = this;
         var lifecycle = self.lifecycle;
@@ -625,8 +636,7 @@ jQuery(function () {
         };
 
         self.lifecycle.undoStateChangedCallback = function () {
-            d3.select(node).select('button.undo').classed('invisible', !self.lifecycle.hasUndoStack());
-            d3.select(node).select('button.redo').classed('invisible', !self.lifecycle.hasRedoStack());
+            self._refreshInspector(false);
         };
         self.lifecycle.undoStateChangedCallback();
 
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 4882ba0..5d031bf 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -704,22 +704,20 @@ jQuery(function () {
         return frame;
     };
 
-    Lifecycle.prototype._saveUndoEntry = function () {
-        var frame = this._currentUndoFrame();
-        this._undoState.undoStack.push(frame);
-        this._undoState.redoStack = [];
+    Lifecycle.prototype._undoStateChanged = function () {
+        this._canUndo = this._undoState.undoStack.length > 0;
+        this._canRedo = this._undoState.redoStack.length > 0;
 
         if (this.undoStateChangedCallback) {
             this.undoStateChangedCallback();
         }
     };
 
-    Lifecycle.prototype.hasUndoStack = function () {
-        return this._undoState.undoStack.length > 0;
-    };
-
-    Lifecycle.prototype.hasRedoStack = function () {
-        return this._undoState.redoStack.length > 0;
+    Lifecycle.prototype._saveUndoEntry = function () {
+        var frame = this._currentUndoFrame();
+        this._undoState.undoStack.push(frame);
+        this._undoState.redoStack = [];
+        this._undoStateChanged();
     };
 
     Lifecycle.prototype._rebuildKeyMap = function () {
@@ -765,9 +763,7 @@ jQuery(function () {
 
         this._restoreState(entry);
 
-        if (this.undoStateChangedCallback) {
-            this.undoStateChangedCallback();
-        }
+        this._undoStateChanged();
 
         return frame;
     };
@@ -785,9 +781,7 @@ jQuery(function () {
 
         this._restoreState(entry);
 
-        if (this.undoStateChangedCallback) {
-            this.undoStateChangedCallback();
-        }
+        this._undoStateChanged();
 
         return frame;
     };

commit f57430221b615a79a7d9d4079901837d0c98665b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 17:55:49 2017 +0000

    Always render undo/redo, but greyed out when unavailable

diff --git a/html/Elements/LifecycleInspectorHeader b/html/Elements/LifecycleInspectorHeader
index 2089bb5..37071f2 100644
--- a/html/Elements/LifecycleInspectorHeader
+++ b/html/Elements/LifecycleInspectorHeader
@@ -1,6 +1,6 @@
 <script type="text/x-template" class="lifecycle-inspector-template" data-type="header">
   <div class="controls">
-    <button class="undo {{#unless lifecycle._canUndo}}invisible{{/unless}}"><&|/l&>Undo</&></button>
-    <button class="redo {{#unless lifecycle._canRedo}}invisible{{/unless}}"><&|/l&>Redo</&></button>
+    <button {{#unless lifecycle._canUndo}}disabled{{/unless}} class="undo"><&|/l&>Undo</&></button>
+    <button {{#unless lifecycle._canRedo}}disabled{{/unless}} class="redo"><&|/l&>Redo</&></button>
   </div>
 </script>
diff --git a/static/css/lifecycleui-editor.css b/static/css/lifecycleui-editor.css
index 799a99a..4ac1fa3 100644
--- a/static/css/lifecycleui-editor.css
+++ b/static/css/lifecycleui-editor.css
@@ -138,3 +138,8 @@
 .lifecycle-ui.editing .decorations .hover.text-background {
     fill: white;
 }
+
+.lifecycle-ui .inspector button:disabled {
+    background: hsl(222, 20%, 40%);
+    cursor: not-allowed;
+}

commit 1bdf8b4cfe91640e0546179f20cbd080467e1337
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 18:09:42 2017 +0000

    Move add/select menu into header so it's always available
    
    It also helps tame the canvas inspector's disproportionate size

diff --git a/html/Elements/LifecycleInspectorCanvas b/html/Elements/LifecycleInspectorCanvas
index 5d2071b..3025d08 100644
--- a/html/Elements/LifecycleInspectorCanvas
+++ b/html/Elements/LifecycleInspectorCanvas
@@ -109,57 +109,5 @@
 	  {{/if}}
         </table>
 
-        <ul class="toplevel">
-          <li><a href="javascript:void(0)" class="menu-item add-status"><&|/l&>Add Status</&></a></li>
-          <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Add Decoration...</&></a>
-              <ul>
-                  <li><a href="javascript:void(0)" class="menu-item add-text"><&|/l&>Add Label</&></a></li>
-                  <li><a href="javascript:void(0)" class="menu-item add-polygon" data-type="Triangle"><&|/l&>Add Triangle</&></a></li>
-                  <li><a href="javascript:void(0)" class="menu-item add-polygon" data-type="Rectangle"><&|/l&>Add Rectangle</&></a></li>
-                  <li><a href="javascript:void(0)" class="menu-item add-circle"><&|/l&>Add Circle</&></a></li>
-                  <li><a href="javascript:void(0)" class="menu-item add-line"><&|/l&>Add Line</&></a></li>
-              </ul>
-          </li>
-          <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Status...</&></a>
-              <ul>
-              {{#each lifecycle.statuses}}
-              <li><a href="javascript:void(0)" class="menu-item select-status" data-name="{{this}}">{{this}}</a></li>
-              {{/each}}
-              </ul>
-           </li>
-          <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Transition...</&></a>
-              <ul>
-              {{#each lifecycle.statuses}}
-              <li class="has-children"><a href="javascript:void(0)" class="menu-item select-status" data-name="{{this}}"><&|/l, "{{this}}"&>from [_1]</&></a>
-                <ul>
-                  {{#with this as |from|}}
-                  {{#with ../lifecycle as |lc|}}
-                  {{#each lc.statuses}}
-                    <li class="menu-item {{#if (canSelectTransition from this lc)}}{{else}}hidden{{/if}}"><a href="#" class="menu-item select-transition" data-from="{{from}}" data-to="{{this}}"><&|/l, "{{this}}"&>to [_1]</&></a></li>
-                  {{/each}}
-                  {{/with}}
-                  {{/with}}
-                </ul>
-              </li>
-              {{/each}}
-              </ul>
-           </li>
-           <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Decoration...</&></a>
-              <ul>
-              {{#each lifecycle.decorations.text}}
-              <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.text}}</a></li>
-              {{/each}}
-              {{#each lifecycle.decorations.polygon}}
-              <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
-              {{/each}}
-              {{#each lifecycle.decorations.circle}}
-              <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
-              {{/each}}
-              {{#each lifecycle.decorations.line}}
-              <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
-              {{/each}}
-              </ul>
-           </li>
-        </ul>
     </div>
 </script>
diff --git a/html/Elements/LifecycleInspectorHeader b/html/Elements/LifecycleInspectorHeader
index 37071f2..1f9f3e7 100644
--- a/html/Elements/LifecycleInspectorHeader
+++ b/html/Elements/LifecycleInspectorHeader
@@ -1,4 +1,63 @@
 <script type="text/x-template" class="lifecycle-inspector-template" data-type="header">
+  <ul class="toplevel">
+    <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Add...</&></a>
+      <ul>
+        <li><a href="javascript:void(0)" class="menu-item add-status"><&|/l&>Add Status</&></a></li>
+        <li><a href="javascript:void(0)" class="menu-item add-text"><&|/l&>Add Label</&></a></li>
+        <li><a href="javascript:void(0)" class="menu-item add-polygon" data-type="Triangle"><&|/l&>Add Triangle</&></a></li>
+        <li><a href="javascript:void(0)" class="menu-item add-polygon" data-type="Rectangle"><&|/l&>Add Rectangle</&></a></li>
+        <li><a href="javascript:void(0)" class="menu-item add-circle"><&|/l&>Add Circle</&></a></li>
+        <li><a href="javascript:void(0)" class="menu-item add-line"><&|/l&>Add Line</&></a></li>
+      </ul>
+    </li>
+    <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select...</&></a>
+      <ul>
+        <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Status...</&></a>
+            <ul>
+            {{#each lifecycle.statuses}}
+            <li><a href="javascript:void(0)" class="menu-item select-status" data-name="{{this}}">{{this}}</a></li>
+            {{/each}}
+            </ul>
+         </li>
+        <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Transition...</&></a>
+            <ul>
+            {{#each lifecycle.statuses}}
+            <li class="has-children"><a href="javascript:void(0)" class="menu-item select-status" data-name="{{this}}"><&|/l, "{{this}}"&>from [_1]</&></a>
+              <ul>
+                {{#with this as |from|}}
+                {{#with ../lifecycle as |lc|}}
+                {{#each lc.statuses}}
+                  <li class="menu-item {{#if (canSelectTransition from this lc)}}{{else}}hidden{{/if}}"><a href="#" class="menu-item select-transition" data-from="{{from}}" data-to="{{this}}"><&|/l, "{{this}}"&>to [_1]</&></a></li>
+                {{/each}}
+                {{/with}}
+                {{/with}}
+              </ul>
+            </li>
+            {{/each}}
+            </ul>
+         </li>
+         <li class="has-children"><a href="javascript:void(0)" class="menu-item"><&|/l&>Select Decoration...</&></a>
+            <ul>
+            {{#each lifecycle.decorations.text}}
+            <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.text}}</a></li>
+            {{/each}}
+            {{#each lifecycle.decorations.polygon}}
+            <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
+            {{/each}}
+            {{#each lifecycle.decorations.circle}}
+            <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
+            {{/each}}
+            {{#each lifecycle.decorations.line}}
+            <li><a href="javascript:void(0)" class="menu-item select-decoration" data-key="{{this._key}}">{{truncate this.label}}</a></li>
+            {{/each}}
+            </ul>
+         </li>
+       </ul>
+    </li>
+  </ul>
+
+  <hr class="clear" />
+
   <div class="controls">
     <button {{#unless lifecycle._canUndo}}disabled{{/unless}} class="undo"><&|/l&>Undo</&></button>
     <button {{#unless lifecycle._canRedo}}disabled{{/unless}} class="redo"><&|/l&>Redo</&></button>
diff --git a/html/Elements/LifecycleInspectorStatus b/html/Elements/LifecycleInspectorStatus
index abc687d..076bf88 100644
--- a/html/Elements/LifecycleInspectorStatus
+++ b/html/Elements/LifecycleInspectorStatus
@@ -38,7 +38,7 @@
           </tr>
 	</table>
 
-        <ul class="toplevel">
+        <ul class="toplevel sf-vertical">
           <li><a href="javascript:void(0)" class="menu-item"><&|/l&>Add Transition...</&></a>
             <ul>
             {{#each lifecycle.statuses}}
diff --git a/static/css/lifecycleui-editor.css b/static/css/lifecycleui-editor.css
index 4ac1fa3..553bc72 100644
--- a/static/css/lifecycleui-editor.css
+++ b/static/css/lifecycleui-editor.css
@@ -26,11 +26,15 @@
 
 .lifecycle-ui .inspector .content {
     padding: 1em;
+    border-top: 1px solid black;
 }
 
-.lifecycle-ui .inspector .header {
-    padding: 1em;
-    background: #EEE;
+.lifecycle-ui .inspector .header .toplevel {
+    padding-top: 1em;
+}
+
+.lifecycle-ui .inspector .header .controls {
+    padding: 0 0 1em 1em;
 }
 
 .lifecycle-ui .inspector input[type=text] {
diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index e004a24..c1f551b 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -68,7 +68,7 @@ jQuery(function () {
             refreshedNode = inspector;
         }
 
-        refreshedNode.find(".toplevel").addClass('sf-menu sf-vertical sf-js-enabled sf-shadow').supersubs().superfish({ speed: 'fast' });
+        refreshedNode.find(".toplevel").addClass('sf-menu sf-js-enabled sf-shadow').supersubs().superfish({ speed: 'fast' });
 
         refreshedNode.find(':checkbox[data-show-hide]').each(function () {
             var field = jQuery(this);

commit 4d2b6423de1c42110c65185a92be37684c912351
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 18:16:41 2017 +0000

    Notify undo state changed later
    
    This way when we, say, re-render the status menu after changing a status
    name, it happens late enough that the menu sees the new name

diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 5d031bf..5e75bc3 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -372,7 +372,7 @@ jQuery(function () {
     Lifecycle.prototype.deleteStatus = function (key) {
         var self = this;
 
-        self._saveUndoEntry();
+        self._saveUndoEntry(false);
 
         var statusName = self.statusNameForKey(key);
         if (!statusName) {
@@ -401,10 +401,12 @@ jQuery(function () {
             }
             return true;
         });
+
+        self._undoStateChanged();
     };
 
     Lifecycle.prototype.addTransition = function (fromStatus, toStatus) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var transition = {
             _key    : _ELEMENT_KEY_SEQ++,
@@ -419,6 +421,8 @@ jQuery(function () {
 
         transition.right = this.defaultRightForTransition(transition);
 
+        this._undoStateChanged();
+
         return transition;
     };
 
@@ -460,7 +464,7 @@ jQuery(function () {
     };
 
     Lifecycle.prototype.deleteTransition = function (key) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         this.transitions = jQuery.grep(this.transitions, function (transition) {
             if (transition._key == key) {
@@ -469,10 +473,12 @@ jQuery(function () {
             return true;
         });
         delete this._keyMap[key];
+
+        this._undoStateChanged();
     };
 
     Lifecycle.prototype.deleteDecoration = function (type, key) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         this.decorations[type] = jQuery.grep(this.decorations[type], function (decoration) {
             if (decoration._key == key) {
@@ -481,6 +487,8 @@ jQuery(function () {
             return true;
         });
         delete this._keyMap[key];
+
+        this._undoStateChanged();
     };
 
     Lifecycle.prototype.itemForKey = function (key) {
@@ -506,7 +514,7 @@ jQuery(function () {
     };
 
     Lifecycle.prototype.deleteActionForTransition = function (transition, key) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         transition.actions = jQuery.grep(transition.actions, function (action) {
             if (action._key == key) {
@@ -515,11 +523,13 @@ jQuery(function () {
             return true;
         });
         delete this._keyMap[key];
+
+        this._undoStateChanged();
     };
 
     Lifecycle.prototype.updateItem = function (item, field, newValue, skipUndo) {
         if (!skipUndo) {
-            this._saveUndoEntry();
+            this._saveUndoEntry(false);
         }
 
         var oldValue = item[field];
@@ -529,10 +539,14 @@ jQuery(function () {
         if (item._type == 'status' && field == 'name') {
             this.updateStatusName(oldValue, newValue);
         }
+
+        if (!skipUndo) {
+            this._undoStateChanged();
+        }
     };
 
     Lifecycle.prototype.createActionForTransition = function (transition) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var action = {
             _type : 'action',
@@ -540,15 +554,18 @@ jQuery(function () {
         };
         transition.actions.push(action);
         this._keyMap[action._key] = action;
+
+        this._undoStateChanged();
+
         return action;
     };
 
     Lifecycle.prototype.beginDragging = function () {
-        this._saveUndoEntry();
+        this._saveUndoEntry(true);
     };
 
     Lifecycle.prototype.beginChangingColor = function () {
-        this._saveUndoEntry();
+        this._saveUndoEntry(true);
     };
 
     Lifecycle.prototype.moveItem = function (item, x, y) {
@@ -567,7 +584,7 @@ jQuery(function () {
     };
 
     Lifecycle.prototype.createStatus = function (x, y) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var name;
         var i = 0;
@@ -592,11 +609,14 @@ jQuery(function () {
 
         this._statusMeta[name] = item;
         this._keyMap[item._key] = item;
+
+        this._undoStateChanged();
+
         return item;
     };
 
     Lifecycle.prototype.createTextDecoration = function (x, y) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var item = {
             _key: _ELEMENT_KEY_SEQ++,
@@ -607,11 +627,13 @@ jQuery(function () {
         };
         this.decorations.text.push(item);
         this._keyMap[item._key] = item;
+
+        this._undoStateChanged();
         return item;
     };
 
     Lifecycle.prototype.createPolygonDecoration = function (x, y, type) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var item = {
             _key: _ELEMENT_KEY_SEQ++,
@@ -628,11 +650,13 @@ jQuery(function () {
         };
         this.decorations.polygon.push(item);
         this._keyMap[item._key] = item;
+
+        this._undoStateChanged();
         return item;
     };
 
     Lifecycle.prototype.createCircleDecoration = function (x, y, r) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var item = {
             _key: _ELEMENT_KEY_SEQ++,
@@ -649,11 +673,13 @@ jQuery(function () {
         };
         this.decorations.circle.push(item);
         this._keyMap[item._key] = item;
+
+        this._undoStateChanged();
         return item;
     };
 
     Lifecycle.prototype.createLineDecoration = function (x, y) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var item = {
             _key: _ELEMENT_KEY_SEQ++,
@@ -668,11 +694,13 @@ jQuery(function () {
         };
         this.decorations.line.push(item);
         this._keyMap[item._key] = item;
+
+        this._undoStateChanged();
         return item;
     };
 
     Lifecycle.prototype.update = function (field, value) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         if (field == 'on_create' || field == 'approved' || field == 'denied' || field == 'reminder_on_open' || field == 'reminder_on_resolve') {
             this.defaults[field] = value;
@@ -683,6 +711,8 @@ jQuery(function () {
         else {
             console.error("Unhandled field in Lifecycle.update: " + field);
         }
+
+        this._undoStateChanged();
     };
 
     Lifecycle.prototype._currentUndoFrame = function () {
@@ -713,11 +743,14 @@ jQuery(function () {
         }
     };
 
-    Lifecycle.prototype._saveUndoEntry = function () {
+    Lifecycle.prototype._saveUndoEntry = function (notify) {
         var frame = this._currentUndoFrame();
         this._undoState.undoStack.push(frame);
         this._undoState.redoStack = [];
-        this._undoStateChanged();
+
+        if (notify) {
+            this._undoStateChanged();
+        }
     };
 
     Lifecycle.prototype._rebuildKeyMap = function () {
@@ -787,7 +820,7 @@ jQuery(function () {
     };
 
     Lifecycle.prototype.cloneItem = function (source, x, y) {
-        this._saveUndoEntry();
+        this._saveUndoEntry(false);
 
         var clone = JSON.parse(JSON.stringify(source));
         clone._key = _ELEMENT_KEY_SEQ++;
@@ -802,6 +835,9 @@ jQuery(function () {
         }
 
         this._keyMap[clone._key] = clone;
+
+        this._undoStateChanged();
+
         return clone;
     };
 

commit d9c232bebc139f15a86e9bb3342aa2dbc8cb5526
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 18:23:18 2017 +0000

    Separate out the different sections of the canvas inspector

diff --git a/html/Elements/LifecycleInspectorCanvas b/html/Elements/LifecycleInspectorCanvas
index 3025d08..bd27321 100644
--- a/html/Elements/LifecycleInspectorCanvas
+++ b/html/Elements/LifecycleInspectorCanvas
@@ -12,7 +12,7 @@
           </tr>
 
           {{#if lifecycle.is_ticket}}
-          <tr>
+          <tr class="section">
             <td colspan=2><&|/l&>Lifecycle on Tickets</&>:</td>
           </tr>
           <tr>
@@ -46,6 +46,9 @@
           </tr>
           {{/if}}
 
+          <tr class="section">
+            <td colspan=2><&|/l&>Defaults</&>:</td>
+          </tr>
           <tr>
             <td><&|/l&>On Create</&>:</td>
             <td><select name="on_create">
diff --git a/static/css/lifecycleui-editor.css b/static/css/lifecycleui-editor.css
index 553bc72..7e83c68 100644
--- a/static/css/lifecycleui-editor.css
+++ b/static/css/lifecycleui-editor.css
@@ -147,3 +147,7 @@
     background: hsl(222, 20%, 40%);
     cursor: not-allowed;
 }
+
+.lifecycle-ui .inspector tr.section td {
+    padding-top: 1em;
+}

commit df52f6a9213e012c56ea044154f62b0946240007
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 19:09:16 2017 +0000

    Add Changes doc

diff --git a/Changes b/Changes
new file mode 100644
index 0000000..5477f9c
--- /dev/null
+++ b/Changes
@@ -0,0 +1,23 @@
+0.02 2017-09-28
+  - for interactive display on tickets, highlight the status that user
+    clicked which opened a menu
+  - add way to resize circle decorations
+  - polish inspector UI
+  - hide approvals and reminders options for non-ticket lifecycles
+  - add more localization tags
+  - stop picking random colors for new statuses
+  - animate new status nodes and decorations into the scene
+  - improve animations around transitions by drawing them from status to status
+  - change the permission entry field from textbox to combobox
+  - avoid rendering issues dragging status nodes over top of eachother
+  - avoid rendering issues with transitions being drawn inside status circles
+  - add new option for selecting whether user can pan and zoom on ticket
+    display
+  - add new option for selecting the initial center point on ticket display:
+    origin, current status, or zoom to fit
+  - hide canvas border on ticket display when sensible
+  - make add/select controls available when any node is selected
+  - disable, don't hide, undo/redo buttons that are unavailable
+
+0.01 2017-09-08
+  - initial release

commit 7a6bc22674729587b806aff3d26f7064df62f216
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Sep 28 19:09:57 2017 +0000

    0.02 releng

diff --git a/MANIFEST b/MANIFEST
index 2f17a61..afc4c30 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,3 +1,4 @@
+Changes
 html/Admin/Lifecycles/Create.html
 html/Admin/Lifecycles/index.html
 html/Admin/Lifecycles/Mappings.html
@@ -10,6 +11,7 @@ html/Elements/LifecycleInspector
 html/Elements/LifecycleInspectorAction
 html/Elements/LifecycleInspectorCanvas
 html/Elements/LifecycleInspectorCircle
+html/Elements/LifecycleInspectorHeader
 html/Elements/LifecycleInspectorLine
 html/Elements/LifecycleInspectorPolygon
 html/Elements/LifecycleInspectorStatus
diff --git a/META.yml b/META.yml
index 194472a..65fe538 100644
--- a/META.yml
+++ b/META.yml
@@ -24,7 +24,7 @@ requires:
   perl: 5.10.1
 resources:
   license: http://opensource.org/licenses/gpl-license.php
-version: 0.01
+version: 0.02
 x_module_install_rtx_version: 0.39
 x_requires_rt: 4.4
 x_requires_rt_plugins:
diff --git a/lib/RT/Extension/LifecycleUI.pm b/lib/RT/Extension/LifecycleUI.pm
index df592a6..f742d58 100644
--- a/lib/RT/Extension/LifecycleUI.pm
+++ b/lib/RT/Extension/LifecycleUI.pm
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use Storable;
 
-our $VERSION = '0.01';
+our $VERSION = '0.02';
 
 RT->AddJavaScript("d3.min.js");
 RT->AddJavaScript("handlebars-4.0.6.min.js");

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list