[Bps-public-commit] rt-extension-lifecycleui branch, master, updated. 73fbec0b0b5c65e2986193b2712ed4146f7cf1be

Shawn Moore shawn at bestpractical.com
Tue Sep 5 18:20:30 EDT 2017


The branch, master has been updated
       via  73fbec0b0b5c65e2986193b2712ed4146f7cf1be (commit)
       via  b0522a8149b07ed94623508f7e7d1661de92d410 (commit)
       via  32decee21c406465abb1961a15a212cf9fd93a42 (commit)
       via  f5ffdebc3bf7657a83623a1f0b4ded8432479da0 (commit)
       via  1f244b1b50e4d204c89bebc3afa0ab3ea9466c6e (commit)
       via  a382ec6672c98aabbc5a6a51affdf15cbe402380 (commit)
       via  3c062c7d18519cd5c460d56343cbf66b3f6a7cb9 (commit)
       via  dd5f407ad52771f7bdd54923889af274f1e83d56 (commit)
       via  78d72d3deed5ed3385394274a0414d4199c8a063 (commit)
       via  4f9439d85a2c9b756c2f6737ab5e252d3a8dd179 (commit)
       via  b395ddd50d701bcda142c2b5ade78285a993e09b (commit)
       via  505128526745a93a6f4ce40d66bff78f7e1ae0eb (commit)
      from  4a279f7579173dbd128003c6cf64322218424001 (commit)

Summary of changes:
 html/Elements/LifecycleInspectorCanvas  |   9 +-
 html/Elements/LifecycleInspectorPolygon |   7 ++
 lib/RT/Extension/LifecycleUI.pm         |  74 ++++++++++----
 static/css/lifecycleui-editor.css       |   9 +-
 static/css/lifecycleui-viewer.css       |   4 +-
 static/js/lifecycleui-editor.js         | 109 +++++++++++++++++++-
 static/js/lifecycleui-model.js          | 176 ++++++++++++++++++++++++++++----
 static/js/lifecycleui-viewer.js         |  41 +++++---
 8 files changed, 364 insertions(+), 65 deletions(-)

- Log -----------------------------------------------------------------
commit 505128526745a93a6f4ce40d66bff78f7e1ae0eb
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 18:47:32 2017 +0000

    Factor out a _SaveLifecycle

diff --git a/lib/RT/Extension/LifecycleUI.pm b/lib/RT/Extension/LifecycleUI.pm
index a016a25..acfb0b1 100644
--- a/lib/RT/Extension/LifecycleUI.pm
+++ b/lib/RT/Extension/LifecycleUI.pm
@@ -41,27 +41,10 @@ sub _CloneLifecycleMaps {
     $maps->{"$clone -> $name"} = { %map };
 }
 
-sub _CreateLifecycle {
+sub _SaveLifecycles {
     my $class = shift;
-    my %args  = @_;
-    my $CurrentUser = $args{CurrentUser};
-
-    my $lifecycles = RT->Config->Get('Lifecycles');
-    my $lifecycle;
-
-    if ($args{Clone}) {
-        $lifecycle = Storable::dclone($lifecycles->{ $args{Clone} });
-        $class->_CloneLifecycleMaps(
-            $lifecycles->{__maps__},
-            $args{Name},
-            $args{Clone},
-        );
-    }
-    else {
-        $lifecycle = { type => $args{Type} };
-    }
-
-    $lifecycles->{$args{Name}} = $lifecycle;
+    my $lifecycles = shift;
+    my $CurrentUser = shift;
 
     my $setting = RT::DatabaseSetting->new($CurrentUser);
     $setting->Load('Lifecycles');
@@ -84,7 +67,35 @@ sub _CreateLifecycle {
 
     RT::Lifecycle->FillCache;
 
-    return (1, $CurrentUser->loc("Lifecycle created"));
+    return 1;
+}
+
+sub _CreateLifecycle {
+    my $class = shift;
+    my %args  = @_;
+    my $CurrentUser = $args{CurrentUser};
+
+    my $lifecycles = RT->Config->Get('Lifecycles');
+    my $lifecycle;
+
+    if ($args{Clone}) {
+        $lifecycle = Storable::dclone($lifecycles->{ $args{Clone} });
+        $class->_CloneLifecycleMaps(
+            $lifecycles->{__maps__},
+            $args{Name},
+            $args{Clone},
+        );
+    }
+    else {
+        $lifecycle = { type => $args{Type} };
+    }
+
+    $lifecycles->{$args{Name}} = $lifecycle;
+
+    my ($ok, $msg) = $class->_SaveLifecycles($lifecycles, $CurrentUser);
+    return ($ok, $msg) if !$ok;
+
+    return (1, $CurrentUser->loc("Lifecycle [_1] created", $args{Name}));
 }
 
 sub CreateLifecycle {

commit b395ddd50d701bcda142c2b5ade78285a993e09b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 18:49:21 2017 +0000

    UpdateLifecycle

diff --git a/lib/RT/Extension/LifecycleUI.pm b/lib/RT/Extension/LifecycleUI.pm
index acfb0b1..464bc8a 100644
--- a/lib/RT/Extension/LifecycleUI.pm
+++ b/lib/RT/Extension/LifecycleUI.pm
@@ -133,6 +133,27 @@ sub CreateLifecycle {
     return $class->_CreateLifecycle(%args);
 }
 
+sub UpdateLifecycle {
+    my $class = shift;
+    my %args = (
+        CurrentUser  => undef,
+        LifecycleObj => undef,
+        NewConfig    => undef,
+        @_,
+    );
+
+    my $CurrentUser = $args{CurrentUser};
+    my $name = $args{LifecycleObj}->Name;
+    my $lifecycles = RT->Config->Get('Lifecycles');
+
+    $lifecycles->{$name} = $args{NewConfig};
+
+    my ($ok, $msg) = $class->_SaveLifecycles($lifecycles, $CurrentUser);
+    return ($ok, $msg) if !$ok;
+
+    return (1, $CurrentUser->loc("Lifecycle [_1] updated", $name));
+}
+
 =head1 NAME
 
 RT-Extension-LifecycleUI - manage lifecycles via admin UI

commit 4f9439d85a2c9b756c2f6737ab5e252d3a8dd179
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 19:27:21 2017 +0000

    Implement snap to grid

diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index a43f332..81707cb 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -257,7 +257,7 @@ jQuery(function () {
     };
 
     Editor.prototype.didDragItem = function (d, node) {
-        this.lifecycle.moveItem(d, this.xScale.invert(d3.event.x), this.yScale.invert(d3.event.y));
+        this.lifecycle.moveItem(d, this.xScaleInvert(d3.event.x), this.yScaleInvert(d3.event.y));
         this.renderDisplay();
     };
 
diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index 544ef6e..959e933 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -1,6 +1,7 @@
 jQuery(function () {
     function Viewer (container) {
         this.statusCircleRadius = 35;
+        this.gridSize = 25;
         this.padding = this.statusCircleRadius;
     };
 
@@ -26,6 +27,12 @@ jQuery(function () {
                  .range([padding, size - padding]);
     };
 
+    Viewer.prototype.gridScale = function (v) { return Math.round(v/this.gridSize) * this.gridSize };
+    Viewer.prototype.xScale = function (x) { return this.gridScale(this._xScale(x)) };
+    Viewer.prototype.yScale = function (y) { return this.gridScale(this._yScale(y)) };
+    Viewer.prototype.xScaleInvert = function (x) { return this._xScale.invert(x) };
+    Viewer.prototype.yScaleInvert = function (y) { return this._yScale.invert(y) };
+
     Viewer.prototype.addZoomBehavior = function () {
         var self = this;
         self._zoom = d3.zoom()
@@ -223,8 +230,8 @@ jQuery(function () {
         self.width  = self.svg.node().getBoundingClientRect().width;
         self.height = self.svg.node().getBoundingClientRect().height;
 
-        self.xScale = self.createScale(self.width, self.padding);
-        self.yScale = self.createScale(self.height, self.padding);
+        self._xScale = self.createScale(self.width, self.padding);
+        self._yScale = self.createScale(self.height, self.padding);
 
         self.lifecycle = new RT.Lifecycle();
         self.lifecycle.initializeFromConfig(config);

commit 78d72d3deed5ed3385394274a0414d4199c8a063
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 19:48:21 2017 +0000

    Round-trip extra status and transition metadata
    
    e.g. status color, transition line style

diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 86c17df..cc5dbdd 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -24,12 +24,11 @@ jQuery(function () {
             if (config[type]) {
                 self.statuses = self.statuses.concat(config[type]);
                 jQuery.each(config[type], function (j, statusName) {
-                    var item = {
-                        _key:  _ELEMENT_KEY_SEQ++,
-                        _type: 'status',
-                        name:  statusName,
-                        type:  type
-                    };
+                    var item = config.statusExtra[statusName] || {};
+                    item._key  = _ELEMENT_KEY_SEQ++;
+                    item._type = 'status';
+                    item.name  = statusName;
+                    item.type  = type;
                     self._statusMeta[statusName] = item;
                     self._keyMap[item._key] = item;
                 });
@@ -64,14 +63,15 @@ jQuery(function () {
                 }
                 else {
                     jQuery.each(toList, function (i, toStatus) {
-                        var transition = {
-                            _key    : _ELEMENT_KEY_SEQ++,
-                            _type   : 'transition',
-                            from    : fromStatus,
-                            to      : toStatus,
-                            style   : 'solid',
-                            actions : []
-                        };
+                        var description = fromStatus + ' -> ' + toStatus;
+                        var transition = config.transitionExtra[description] || {};
+                        transition._key    = _ELEMENT_KEY_SEQ++;
+                        transition._type   = 'transition';
+                        transition.from    = fromStatus;
+                        transition.to      = toStatus;
+                        transition.style   = transition.style || 'solid';
+                        transition.actions = [];
+
                         self.transitions.push(transition);
                         self._keyMap[transition._key] = transition;
                     });
@@ -165,6 +165,32 @@ jQuery(function () {
         return 'ModifyTicket';
     };
 
+    Lifecycle.prototype._sanitizeForExport = function (o) {
+        var clone = jQuery.extend(true, {}, o);
+        var type = o._type;
+        jQuery.each(clone, function (key, value) {
+            if (key.substr(0, 1) == '_') {
+                delete clone[key];
+            }
+        });
+
+        // remove additional redundant information to provide a single source
+        // of truth
+        if (type == 'status') {
+            delete clone.name;
+            delete clone.type;
+            delete clone.creation;
+        }
+        else if (type == 'transition') {
+            delete clone.from;
+            delete clone.to;
+            delete clone.actions;
+            delete clone.right;
+        }
+
+        return clone;
+    };
+
     Lifecycle.prototype.exportAsConfiguration = function () {
         var self = this;
         var config = {
@@ -174,7 +200,10 @@ jQuery(function () {
             defaults: self.defaults,
             actions: [],
             rights: {},
-            transitions: self.transitions
+            transitions: self.transitions,
+
+            statusExtra: {},
+            transitionExtra: {}
         };
 
         config.type = self.type;
@@ -182,9 +211,12 @@ jQuery(function () {
         var transitions = { "": [] };
 
         jQuery.each(self.statuses, function (i, statusName) {
-            var statusType = self._statusMeta[statusName].type;
+            var meta = self._statusMeta[statusName];
+            var statusType = meta.type;
             config[statusType].push(statusName);
-            if (self._statusMeta[statusName].creation) {
+            config.statusExtra[statusName] = self._sanitizeForExport(meta);
+
+            if (meta.creation) {
                 transitions[""].push(statusName);
             }
         });
@@ -194,6 +226,8 @@ jQuery(function () {
             var to = transition.to;
             var description = transition.from + ' -> ' + transition.to;
 
+            config.transitionExtra[description] = self._sanitizeForExport(transition);
+
             if (!transitions[from]) {
                 transitions[from] = [];
             }

commit dd5f407ad52771f7bdd54923889af274f1e83d56
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 20:14:18 2017 +0000

    Implement adding new statuses

diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index 81707cb..cfe4ae8 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -184,6 +184,11 @@ jQuery(function () {
 
             self.selectTransition(fromStatus, toStatus);
         });
+
+        inspector.on('click', '.add-status', function (e) {
+            e.preventDefault();
+            self.addNewStatus();
+        });
     };
 
     Editor.prototype.deselectAll = function (clearSelection) {
@@ -284,6 +289,12 @@ jQuery(function () {
         polygons.call(this._createDrag());
     };
 
+    Editor.prototype.addNewStatus = function () {
+        var status = this.lifecycle.createStatus();
+        this.renderDisplay();
+        this.selectStatus(status.name);
+    };
+
     Editor.prototype.initializeEditor = function (node, config) {
         var self = this;
         self.initializeViewer(node, config);
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index cc5dbdd..cefffad 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -462,6 +462,33 @@ jQuery(function () {
         item.y = y;
     };
 
+    Lifecycle.prototype.createStatus = function () {
+        var name;
+        var i = 0;
+        while (1) {
+            name = 'status #' + ++i;
+            if (!this._statusMeta[name]) {
+                break;
+            }
+        }
+
+        this.statuses.push(name);
+
+        var item = {
+            _key: _ELEMENT_KEY_SEQ++,
+            _type: 'status',
+            name:  name,
+            type:  'initial',
+            x:     0.5,
+            y:     0.5,
+        };
+        item.color = defaultColors(item._key);
+
+        this._statusMeta[name] = item;
+        this._keyMap[item._key] = item;
+        return item;
+    };
+
     RT.Lifecycle = Lifecycle;
 });
 

commit 3c062c7d18519cd5c460d56343cbf66b3f6a7cb9
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 20:34:05 2017 +0000

    Have render methods differentiate between initial load and later additions

diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index 959e933..3b1b7d7 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -58,7 +58,7 @@ jQuery(function () {
     Viewer.prototype.didEnterTextDecorations = function (labels) { };
     Viewer.prototype.didEnterPolygonDecorations = function (polygons) { };
 
-    Viewer.prototype.renderStatusNodes = function () {
+    Viewer.prototype.renderStatusNodes = function (initial) {
         var self = this;
         var statuses = self.statusContainer.selectAll("circle")
                                            .data(self.lifecycle.statusObjects(), function (d) { return d._key });
@@ -98,7 +98,7 @@ jQuery(function () {
         }
     };
 
-    Viewer.prototype.renderStatusLabels = function () {
+    Viewer.prototype.renderStatusLabels = function (initial) {
         var self = this;
         var labels = self.statusContainer.selectAll("text")
                                          .data(self.lifecycle.statusObjects(), function (d) { return d._key });
@@ -131,7 +131,7 @@ jQuery(function () {
       return "M" + this.xScale(from.x) + "," + this.yScale(from.y) + "A" + dr + "," + dr + " 0 0,1 " + this.xScale(to.x) + "," + this.yScale(to.y);
     };
 
-    Viewer.prototype.renderTransitions = function () {
+    Viewer.prototype.renderTransitions = function (initial) {
         var self = this;
         var paths = self.transitionContainer.selectAll("path")
                         .data(self.lifecycle.transitions, function (d) { return d._key });
@@ -154,7 +154,7 @@ jQuery(function () {
                       .classed("dotted", function (d) { return d.style == 'dotted' })
     };
 
-    Viewer.prototype.renderTextDecorations = function () {
+    Viewer.prototype.renderTextDecorations = function (initial) {
         var self = this;
         var labels = self.decorationContainer.selectAll("text")
                          .data(self.lifecycle.decorations.text, function (d) { return d._key });
@@ -177,7 +177,7 @@ jQuery(function () {
                       .text(function (d) { return d.text });
     };
 
-    Viewer.prototype.renderPolygonDecorations = function () {
+    Viewer.prototype.renderPolygonDecorations = function (initial) {
         var self = this;
         var polygons = self.decorationContainer.selectAll("polygon")
                            .data(self.lifecycle.decorations.polygon, function (d) { return d._key });
@@ -205,16 +205,16 @@ jQuery(function () {
                      });
     };
 
-    Viewer.prototype.renderDecorations = function () {
-        this.renderPolygonDecorations();
-        this.renderTextDecorations();
+    Viewer.prototype.renderDecorations = function (initial) {
+        this.renderPolygonDecorations(initial);
+        this.renderTextDecorations(initial);
     };
 
-    Viewer.prototype.renderDisplay = function () {
-        this.renderTransitions();
-        this.renderStatusNodes();
-        this.renderStatusLabels();
-        this.renderDecorations();
+    Viewer.prototype.renderDisplay = function (initial) {
+        this.renderTransitions(initial);
+        this.renderStatusNodes(initial);
+        this.renderStatusLabels(initial);
+        this.renderDecorations(initial);
     };
 
     Viewer.prototype.initializeViewer = function (node, config) {
@@ -239,7 +239,7 @@ jQuery(function () {
         self.createArrowHead();
         self.addZoomBehavior();
 
-        self.renderDisplay();
+        self.renderDisplay(true);
     };
 
     RT.LifecycleViewer = Viewer;

commit a382ec6672c98aabbc5a6a51affdf15cbe402380
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 20:36:41 2017 +0000

    Implement adding text decorations

diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index cfe4ae8..0b13093 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -189,6 +189,11 @@ jQuery(function () {
             e.preventDefault();
             self.addNewStatus();
         });
+
+        inspector.on('click', '.add-text', function (e) {
+            e.preventDefault();
+            self.addNewTextDecoration();
+        });
     };
 
     Editor.prototype.deselectAll = function (clearSelection) {
@@ -295,6 +300,12 @@ jQuery(function () {
         this.selectStatus(status.name);
     };
 
+    Editor.prototype.addNewTextDecoration = function () {
+        var text = this.lifecycle.createTextDecoration();
+        this.renderDisplay();
+        this.selectDecoration(text._key);
+    };
+
     Editor.prototype.initializeEditor = function (node, config) {
         var self = this;
         self.initializeViewer(node, config);
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index cefffad..285668e 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -489,6 +489,19 @@ jQuery(function () {
         return item;
     };
 
+    Lifecycle.prototype.createTextDecoration = function () {
+        var item = {
+            _key: _ELEMENT_KEY_SEQ++,
+            _type: 'text',
+            text:  'New label',
+            x:     0.5,
+            y:     0.5,
+        };
+        this.decorations.text.push(item);
+        this._keyMap[item._key] = item;
+        return item;
+    };
+
     RT.Lifecycle = Lifecycle;
 });
 

commit 1f244b1b50e4d204c89bebc3afa0ab3ea9466c6e
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 20:43:57 2017 +0000

    Round-trip decorations

diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 285668e..c5edaa7 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -145,12 +145,22 @@ jQuery(function () {
             }
         }
 
-        if (config.decorations) {
-            self.decorations = config.decorations;
-        }
+        self.decorations = {};
+
+        jQuery.each(['text', 'polygon'], function (i, type) {
+            var decorations = [];
+
+            if (config.decorations[type]) {
+                jQuery.each(config.decorations[type], function (i, decoration) {
+                    decoration._key = _ELEMENT_KEY_SEQ++;
+                    decoration._type = type;
+                    decorations.push(decoration);
+                    self._keyMap[decoration._key] = decoration;
+                });
+            }
 
-        self.decorations.text = self.decorations.text || [];
-        self.decorations.polygon = self.decorations.polygon || [];
+            self.decorations[type] = decorations;
+        });
     };
 
     Lifecycle.prototype.defaultRightForTransition = function (transition) {
@@ -202,6 +212,7 @@ jQuery(function () {
             rights: {},
             transitions: self.transitions,
 
+            decorations: {},
             statusExtra: {},
             transitionExtra: {}
         };
@@ -250,6 +261,15 @@ jQuery(function () {
 
         config.transitions = transitions;
 
+        config.decorations = {};
+        jQuery.each(self.decorations, function (type, decorations) {
+            var out = [];
+            jQuery.each(decorations, function (i, decoration) {
+                out.push(self._sanitizeForExport(decoration));
+            });
+            config.decorations[type] = out;
+        });
+
         return config;
     };
 

commit f5ffdebc3bf7657a83623a1f0b4ded8432479da0
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 21:34:44 2017 +0000

    Implement dashed/dotted polygons

diff --git a/html/Elements/LifecycleInspectorPolygon b/html/Elements/LifecycleInspectorPolygon
index e7092b3..2285fcf 100644
--- a/html/Elements/LifecycleInspectorPolygon
+++ b/html/Elements/LifecycleInspectorPolygon
@@ -1,6 +1,13 @@
 <script type="text/x-template" class="lifecycle-inspector-template" data-type="polygon">
     <div class="polygon">
         Stroke: <input type="checkbox" name="renderStroke" {{#if polygon.renderStroke}}checked=checked{{/if}} data-show-hide=".color-control[data-field=stroke]"> <span class="color-control" data-field="stroke"><span class="current-color" title="{{polygon.stroke}}" style="background-color: {{polygon.stroke}}"> </span> <button class="change-color"><&|/l&>Change</&></button></span><br>
+       Style: <select name="strokeStyle">
+                 {{#select polygon.strokeStyle}}
+                 <option value="solid"><&|/l&>solid</&></option>
+                 <option value="dashed"><&|/l&>dashed</&></option>
+                 <option value="dotted"><&|/l&>dotted</&></option>
+                 {{/select}}
+             </select><br>
         Fill: <input type="checkbox" name="renderFill" {{#if polygon.renderFill}}checked=checked{{/if}} data-show-hide=".color-control[data-field=fill]"> <span class="color-control" data-field="fill"><span class="current-color" title="{{polygon.fill}}" style="background-color: {{polygon.fill}}"> </span> <button class="change-color"><&|/l&>Change</&></button></span><br>
         <button class="delete"><&|/l&>Delete Polygon</&></button>
     </div>
diff --git a/static/css/lifecycleui-viewer.css b/static/css/lifecycleui-viewer.css
index e500f20..13dac4e 100644
--- a/static/css/lifecycleui-viewer.css
+++ b/static/css/lifecycleui-viewer.css
@@ -17,11 +17,11 @@
     marker-end: url(#marker_arrowhead);
 }
 
-.lifecycle-ui .transitions path.dotted {
+.lifecycle-ui .dotted {
     stroke-dasharray: 2;
 }
 
-.lifecycle-ui .transitions path.dashed {
+.lifecycle-ui .dashed {
     stroke-dasharray: 5;
 }
 
diff --git a/static/js/lifecycleui-viewer.js b/static/js/lifecycleui-viewer.js
index 3b1b7d7..3b582db 100644
--- a/static/js/lifecycleui-viewer.js
+++ b/static/js/lifecycleui-viewer.js
@@ -196,6 +196,8 @@ jQuery(function () {
                      .call(function (polygons) { self.didEnterPolygonDecorations(polygons) })
               .merge(polygons)
                      .attr("stroke", function (d) { return d.renderStroke ? d.stroke : 'none' })
+                      .classed("dashed", function (d) { return d.strokeStyle == 'dashed' })
+                      .classed("dotted", function (d) { return d.strokeStyle == 'dotted' })
                      .attr("fill", function (d) { return d.renderFill ? d.fill : 'none' })
                      .attr("transform", function (d) { return "translate(" + self.xScale(d.x) + ", " + self.yScale(d.y) + ")" })
                      .attr("points", function (d) {

commit 32decee21c406465abb1961a15a212cf9fd93a42
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 21:46:36 2017 +0000

    Add polygons (currently triangles and rectangles)

diff --git a/html/Elements/LifecycleInspectorCanvas b/html/Elements/LifecycleInspectorCanvas
index 2146191..f5d091f 100644
--- a/html/Elements/LifecycleInspectorCanvas
+++ b/html/Elements/LifecycleInspectorCanvas
@@ -2,9 +2,14 @@
     <div class="canvas">
         <button class="add-status"><&|/l&>Add Status</&></button><br>
         <button class="add-text"><&|/l&>Add Text</&></button><br>
-        <button class="add-polygon"><&|/l&>Add Polygon</&></button><br>
     <br>
         <ul class="toplevel sf-menu sf-vertical sf-js-enabled sf-shadow">
+          <li class="has-children">Add Shape
+              <ul>
+                  <li><a href="#" class="add-polygon" data-type="triangle">Add Triangle</a></li>
+                  <li><a href="#" class="add-polygon" data-type="rectangle">Add Rectangle</a></li>
+              </ul>
+          </li>
           <li class="has-children">Select Status
               <ul>
               {{#each lifecycle.statuses}}
@@ -14,7 +19,7 @@
            </li>
            <li class="has-children">Select Transition</li>
            <li class="has-children">Select Text</li>
-           <li class="has-children">Select Polygon</li>
+           <li class="has-children">Select Shape</li>
         </ul>
     </div>
 </script>
diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index 0b13093..d2410f6 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -194,6 +194,11 @@ jQuery(function () {
             e.preventDefault();
             self.addNewTextDecoration();
         });
+
+        inspector.on('click', '.add-polygon', function (e) {
+            e.preventDefault();
+            self.addNewPolygonDecoration(jQuery(this).data('type'));
+        });
     };
 
     Editor.prototype.deselectAll = function (clearSelection) {
@@ -306,6 +311,12 @@ jQuery(function () {
         this.selectDecoration(text._key);
     };
 
+    Editor.prototype.addNewPolygonDecoration = function (type) {
+        var text = this.lifecycle.createPolygonDecoration(type);
+        this.renderDisplay();
+        this.selectDecoration(text._key);
+    };
+
     Editor.prototype.initializeEditor = function (node, config) {
         var self = this;
         self.initializeViewer(node, config);
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index c5edaa7..5089d6d 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -11,6 +11,20 @@ jQuery(function () {
 
         this._keyMap = {};
         this._statusMeta = {};
+
+        this._initialPointsForPolygon = {
+            triangle: [
+                {x:  .07, y: .2},
+                {x:    0, y:  0},
+                {x: -.06, y: .2}
+            ],
+            rectangle: [
+                {x: -.06, y: -.06},
+                {x:  .06, y: -.06},
+                {x:  .06, y:  .06},
+                {x: -.06, y:  .06}
+            ]
+        };
     };
 
     Lifecycle.prototype.initializeFromConfig = function (config) {
@@ -522,6 +536,24 @@ jQuery(function () {
         return item;
     };
 
+    Lifecycle.prototype.createPolygonDecoration = function (type) {
+        var item = {
+            _key: _ELEMENT_KEY_SEQ++,
+            _type: 'polygon',
+            stroke: '#000000',
+            renderStroke: true,
+            strokeStyle: 'solid',
+            fill: '#ffffff',
+            renderFill: true,
+            x: 0.5,
+            y: 0.5,
+            points: this._initialPointsForPolygon[type]
+        };
+        this.decorations.polygon.push(item);
+        this._keyMap[item._key] = item;
+        return item;
+    };
+
     RT.Lifecycle = Lifecycle;
 });
 

commit b0522a8149b07ed94623508f7e7d1661de92d410
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 21:47:15 2017 +0000

    Add drag handles for each point in the selected polygon

diff --git a/static/css/lifecycleui-editor.css b/static/css/lifecycleui-editor.css
index becc4d4..0575c64 100644
--- a/static/css/lifecycleui-editor.css
+++ b/static/css/lifecycleui-editor.css
@@ -71,10 +71,17 @@
     opacity: .2;
 }
 
-.lifecycle-ui .selection .decorations > .selected {
+.lifecycle-ui .selection .decorations > .selected,
+.lifecycle-ui .selection .decorations > .point-handle {
     opacity: 1;
 }
 
+.lifecycle-ui .selection .point-handle {
+    stroke: black;
+    fill: steelblue;
+    r: 5px;
+}
+
 .lifecycle-ui .inspector li.has-children {
     background: transparent;
 }
diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index d2410f6..22d3cdf 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -209,6 +209,8 @@ jQuery(function () {
         svg.selectAll('.selected-sink').classed('selected-sink', false);
         svg.selectAll('.reachable').classed('reachable', false);
 
+        this.removePointHandles();
+
         if (clearSelection) {
             this.setInspectorContent(null);
         }
@@ -257,6 +259,67 @@ jQuery(function () {
         this.svg.classed('selection', true);
         this.decorationContainer.selectAll('*[data-key="'+key+'"]').classed('selected', true);
         this.setInspectorContent(d);
+
+        if (d._type == 'polygon') {
+            this.addPointHandles(d);
+        }
+
+        this.renderDisplay();
+    };
+
+    Editor.prototype.addPointHandles = function (d) {
+        var points = [];
+        for (var i = 0; i < d.points.length; ++i) {
+            points.push({
+                i: i,
+                x: d.points[i].x,
+                y: d.points[i].y
+            });
+        }
+        this.pointHandles = points;
+    };
+
+    Editor.prototype.removePointHandles = function () {
+        if (!this.pointHandles) {
+            return;
+        }
+
+        delete this.pointHandles;
+        this.renderPolygonDecorations();
+    };
+
+    Editor.prototype.didDragPointHandle = function (d, node) {
+        var x = this.xScaleInvert(d3.event.x);
+        var y = this.yScaleInvert(d3.event.y);
+
+        d.x = x;
+        d.y = y;
+
+        this.lifecycle.movePolygonPoint(this.inspectorNode, d.i, x, y);
+
+        this.renderDisplay();
+    };
+
+    Editor.prototype.renderPolygonDecorations = function (initial) {
+        Super.prototype.renderPolygonDecorations.call(this, initial);
+
+        var self = this;
+        var handles = self.decorationContainer.selectAll("circle")
+                           .data(self.pointHandles || [], function (d) { return d.i });
+
+        handles.exit()
+              .remove();
+
+        handles.enter().append("circle")
+                     .classed("point-handle", true)
+                     .call(d3.drag()
+                         .subject(function (d) { return { x: self.xScale(d.x), y : self.yScale(d.y) } })
+                         .on("drag", function (d) { self.didDragPointHandle(d) })
+                     )
+              .merge(handles)
+                     .attr("transform", function (d) { return "translate(" + self.xScale(self.inspectorNode.x) + ", " + self.yScale(self.inspectorNode.y) + ")" })
+                     .attr("cx", function (d) { return self.xScale(d.x) })
+                     .attr("cy", function (d) { return self.yScale(d.y) })
     };
 
     Editor.prototype.clickedStatus = function (d) {
diff --git a/static/js/lifecycleui-model.js b/static/js/lifecycleui-model.js
index 5089d6d..f63797a 100644
--- a/static/js/lifecycleui-model.js
+++ b/static/js/lifecycleui-model.js
@@ -496,6 +496,12 @@ jQuery(function () {
         item.y = y;
     };
 
+    Lifecycle.prototype.movePolygonPoint = function (polygon, index, x, y) {
+        var point = polygon.points[index];
+        point.x = x;
+        point.y = y;
+    };
+
     Lifecycle.prototype.createStatus = function () {
         var name;
         var i = 0;

commit 73fbec0b0b5c65e2986193b2712ed4146f7cf1be
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Sep 5 21:53:57 2017 +0000

    Avoid rerendering display for each pixel of drag
    
    Now we only rerender when (because of snap to grid) there's an actual
    change in position

diff --git a/static/js/lifecycleui-editor.js b/static/js/lifecycleui-editor.js
index 22d3cdf..55e8b9d 100644
--- a/static/js/lifecycleui-editor.js
+++ b/static/js/lifecycleui-editor.js
@@ -292,6 +292,10 @@ jQuery(function () {
         var x = this.xScaleInvert(d3.event.x);
         var y = this.yScaleInvert(d3.event.y);
 
+        if (this.xScale(x) == this.xScale(d.x) && this.yScale(y) == this.yScale(d.y)) {
+            return;
+        }
+
         d.x = x;
         d.y = y;
 
@@ -335,7 +339,14 @@ jQuery(function () {
     };
 
     Editor.prototype.didDragItem = function (d, node) {
-        this.lifecycle.moveItem(d, this.xScaleInvert(d3.event.x), this.yScaleInvert(d3.event.y));
+        var x = this.xScaleInvert(d3.event.x);
+        var y = this.yScaleInvert(d3.event.y);
+
+        if (this.xScale(x) == this.xScale(d.x) && this.yScale(y) == this.yScale(d.y)) {
+            return;
+        }
+
+        this.lifecycle.moveItem(d, x, y);
         this.renderDisplay();
     };
 

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


More information about the Bps-public-commit mailing list