[Rt-commit] rt branch, 4.6/lifecycle-ui-dev, repushed

Craig Kaiser craig at bestpractical.com
Tue Nov 19 16:49:51 EST 2019


The branch 4.6/lifecycle-ui-dev was deleted and repushed:
       was db4a3b570bb07b709ae6b9ac0afe01c79a2cca35
       now f45bc0d57a62f0da5b43836f73d5dbe86a9eefed

1: 86f8aa51f6 ! 1: f45bc0d57a Core Lifecycle-UI
    @@ -1,6 +1,119 @@
     Author: Craig Kaiser <craig at bestpractical.com>
     
         Core Lifecycle-UI
    +
    +diff --git a/etc/acl.Pg b/etc/acl.Pg
    +--- a/etc/acl.Pg
    ++++ b/etc/acl.Pg
    +@@
    +         CustomRoles
    +         objectcustomroles_id_seq
    +         ObjectCustomRoles
    ++        databasesettings_id_seq
    ++        DatabaseSettings
    +     );
    + 
    +     my $db_user = RT->Config->Get('DatabaseUser');
    +
    +diff --git a/etc/schema.Oracle b/etc/schema.Oracle
    +--- a/etc/schema.Oracle
    ++++ b/etc/schema.Oracle
    +@@
    +         LastUpdated     DATE
    + );
    + CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
    ++
    ++CREATE SEQUENCE DatabaseSettings_seq;
    ++CREATE TABLE DatabaseSettings (
    ++    id              NUMBER(11,0)    CONSTRAINT DatabaseSettings_key PRIMARY KEY,
    ++    Name            VARCHAR2(255) CONSTRAINT DatabaseSettings_Name_Unique unique  NOT NULL,
    ++    Content         CLOB,
    ++    ContentType     VARCHAR2(80),
    ++    Disabled        NUMBER(11,0) DEFAULT 0 NOT NULL,
    ++    Creator         NUMBER(11,0)    DEFAULT 0 NOT NULL,
    ++    Created         DATE,
    ++    LastUpdatedBy   NUMBER(11,0)    DEFAULT 0 NOT NULL,
    ++    LastUpdated     DATE
    ++);
    ++
    ++CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
    ++CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
    ++
    +
    +diff --git a/etc/schema.Pg b/etc/schema.Pg
    +--- a/etc/schema.Pg
    ++++ b/etc/schema.Pg
    +@@
    + );
    + 
    + CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
    ++
    ++CREATE SEQUENCE databasesettings_id_seq;
    ++CREATE TABLE DatabaseSettings (
    ++    id                integer         DEFAULT nextval('databasesettings_id_seq'),
    ++    Name              varchar(255)    NOT NULL,
    ++    Content           text            NULL,
    ++    ContentType       varchar(80)     NULL,
    ++    Disabled          integer         NOT NULL DEFAULT 0 ,
    ++    Creator           integer         NOT NULL DEFAULT 0,
    ++    Created           timestamp                DEFAULT NULL,
    ++    LastUpdatedBy     integer         NOT NULL DEFAULT 0,
    ++    LastUpdated       timestamp                DEFAULT NULL,
    ++    PRIMARY KEY (id)
    ++);
    ++
    ++CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (LOWER(Name));
    ++CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
    ++
    +
    +diff --git a/etc/schema.SQLite b/etc/schema.SQLite
    +--- a/etc/schema.SQLite
    ++++ b/etc/schema.SQLite
    +@@
    +   PRIMARY KEY (id)
    + );
    + CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
    ++
    ++CREATE TABLE DatabaseSettings (
    ++    id                INTEGER PRIMARY KEY,
    ++    Name              varchar(255)    collate NOCASE NOT NULL,
    ++    Content           longtext        collate NOCASE NULL,
    ++    ContentType       varchar(80)     collate NOCASE NULL,
    ++    Disabled          int2            NOT NULL DEFAULT 0,
    ++    Creator           int(11)         NOT NULL DEFAULT 0,
    ++    Created           timestamp                DEFAULT NULL,
    ++    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
    ++    LastUpdated       timestamp                DEFAULT NULL
    ++);
    ++
    ++CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
    ++CREATE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
    ++
    +
    +diff --git a/etc/schema.mysql b/etc/schema.mysql
    +--- a/etc/schema.mysql
    ++++ b/etc/schema.mysql
    +@@
    + ) ENGINE=InnoDB CHARACTER SET utf8;
    + 
    + CREATE UNIQUE INDEX ObjectCustomRoles1 ON ObjectCustomRoles (ObjectId, CustomRole);
    ++
    ++CREATE TABLE DatabaseSettings (
    ++    id                int(11)         NOT NULL AUTO_INCREMENT,
    ++    Name              varchar(255)    NOT NULL,
    ++    Content           longblob        NULL,
    ++    ContentType       varchar(80)     CHARACTER SET ascii NULL,
    ++    Disabled          int2            NOT NULL DEFAULT 0,
    ++    Creator           int(11)         NOT NULL DEFAULT 0,
    ++    Created           datetime                 DEFAULT NULL,
    ++    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
    ++    LastUpdated       datetime                 DEFAULT NULL,
    ++    PRIMARY KEY (id)
    ++) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    ++
    ++CREATE UNIQUE INDEX DatabaseSettings1 ON DatabaseSettings (Name);
    ++CREATE UNIQUE INDEX DatabaseSettings2 ON DatabaseSettings (Disabled);
    ++
     
     diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
     --- a/lib/RT/Interface/Web.pm
    @@ -925,20 +1038,54 @@
     +%#
     +%# END BPS TAGGED BLOCK }}}
     +<div class="lifecycle-ui" id="lifecycle-<% $id %>">
    -+    <div class="lifecycle-ui">
    -+      <svg>
    -+      </svg>
    ++  <div class="row">
    ++    <div class="col-md-7"></div>
    ++    <div class="col-md-5">
    ++      <div id="lifeycycle-ui-edit-node" class="lifeycycle-ui-edit-node collapse card card-body">
    ++        <input type="hidden" name="id" />
    ++        <div class="row">
    ++          <div class="col-md-3 label">
    ++            <label for="name">Name:</label>
    ++          </div>
    ++          <div class="col-md-9 value">
    ++            <input class="form-control" type="text" id="name" name="name" value="" />
    ++          </div>
    ++        </div>
    ++
    ++        <div class="row">
    ++          <div class="col-md-3 label">
    ++            <label for="type">Type:</label>
    ++          </div>
    ++          <div class="col-md-9 value">
    ++            <select class="selectpicker form-control" id="type" name="type">
    ++              <option value="initial">initial</option>
    ++              <option value="active">active</option>
    ++              <option value="inactive">inactive</option>
    ++            </select>
    ++          </div>
    ++        </div>
    ++        <div class="row float-right">
    ++          <div class="col-md-12">
    ++            <button class="button btn btn-primary form-control" id="SaveNode">Save</button>
    ++          </div>
    ++        </div>
    ++      </div>
     +    </div>
    -+
    -+    <script type="text/javascript">
    -+        jQuery(function () {
    -+            var container = document.getElementById('lifecycle-<% $id %>'),
    -+                   config = <% JSON($config) |n %>,
    -+                   name   = <% $Lifecycle | j%>;
    -+
    -+            var editor = new RT.NewEditor( container, config );
    -+        });
    -+    </script>
    ++  </div>
    ++
    ++    <svg>
    ++    </svg>
    ++  </div>
    ++
    ++  <script type="text/javascript">
    ++    jQuery(function () {
    ++      var container = document.getElementById('lifecycle-<% $id %>'),
    ++        config = <% JSON($config) |n %>,
    ++        name   = <% $Lifecycle | j%>;
    ++
    ++        var editor = new RT.NewEditor( container, config );
    ++    });
    ++  </script>
     +</div>
     +
     +<%INIT>
    @@ -1334,6 +1481,10 @@
     +    stroke-width: 2px;
     +    cursor: pointer;
     +}
    ++
    ++.lifeycycle-ui-edit-node {
    ++    position: absolute;
    ++}
     
     diff --git a/share/static/css/elevator-light/main.css b/share/static/css/elevator-light/main.css
     --- a/share/static/css/elevator-light/main.css
    @@ -1367,6 +1518,11 @@
     +            self.width       = 960;
     +            self.height      = 350;
     +            self.node_radius = 35;
    ++
    ++            jQuery("#SaveNode").click(function( event ) {
    ++                event.preventDefault();
    ++                self.UpdateNode();
    ++            });
     +
     +            self.svg      = d3.select(container).select('svg')
     +                .attr("preserveAspectRatio", "xMinYMin meet")
    @@ -1378,6 +1534,7 @@
     +            self.animationFactor = 1;
     +            // mouse event vars
     +            self.selected_node   = null;
    ++            self.editing_node    = null;
     +            self.selected_link   = null;
     +            self.mousedown_link  = null;
     +            self.mousedown_node  = null;
    @@ -1503,11 +1660,12 @@
     +                    d3.event.preventDefault();
     +                    d3.event.stopPropagation();
     +
    -+                    if ( self.selected_node ) {
    ++                    if ( self.selected_node || self.editing_node ) {
     +                        self.Deselect();
     +                    }
     +                    else {
     +                        self.simulation.stop();
    ++                        self.Deselect();
     +                        self.AddNode(d3.mouse(this));
     +
     +                        self.ExportAsConfiguration();
    @@ -1521,7 +1679,7 @@
     +                .on('mousedown', function() { self.Mousedown(this); });
     +
     +            d3.select("body").on("keydown", function (d) {
    -+                if ( self.selected_node && ( d3.event.keyCode == 68 || d3.event.keyCode == 46 ) ) {
    ++                if ( !self.editing_node && self.selected_node && ( d3.event.keyCode == 68 || d3.event.keyCode == 46 ) ) {
     +                    d3.event.preventDefault();
     +                    d3.event.stopPropagation();
     +
    @@ -1624,76 +1782,38 @@
     +                .on("click", function(d) {
     +                    d3.event.stopPropagation();
     +                    d3.event.preventDefault();
    -+                    self.RenderNodeInput(this, d)
    ++                    self.UpdateNode(d);
     +                });
     +
     +            self.node.select("title")
     +                .text(function(d) { return d.type; });
     +        }
     +
    -+        RenderNodeInput(elem, d) {
    ++        UpdateNode(element) {
     +            var self = this;
     +
    -+            var p_el = d3.select(elem.parentNode);
    -+            var el   = d3.select(elem);
    -+
    -+            // Clear the text value while we edit
    -+            var original_value = el.text();
    -+            el.text('');
    -+
    -+            var index = self.nodes.findIndex(x => x.id == d.id);
    -+
    -+            var frm = p_el.append("foreignObject");
    -+            var input = frm
    -+                .attr("x", -22.5)
    -+                .attr("y", -15)
    -+                .attr("width", 450)
    -+                .attr("height", 250)
    -+                .style("font-size", "10px")
    -+                .append("xhtml:form")
    -+                    .append("input")
    -+                        .on("click", d => {d3.event.stopPropagation(); d3.event.preventDefault();})
    -+                        .attr("value", d => original_value)
    -+                        .attr("style", "width: 35px; background: transparent; border: none;")
    -+                .on("blur", function(d) {
    -+                    var text = input.node().value;
    -+                    if ( text && text != original_value && !self.nodes.filter(n => n.name === text)[0] ) {
    -+                        self.UpdateNodeStatus(self.nodes[index], text);
    -+                        el.text(text);
    ++            const nodeInput = jQuery("#lifeycycle-ui-edit-node");
    ++            var list = document.getElementById('lifeycycle-ui-edit-node').querySelectorAll('input, select');
    ++
    ++            if ( element ) {
    ++                for (let item of list) {
    ++                    jQuery(item).val(element[item.name]);
    ++                }
    ++                self.editing_node = element;
    ++            }
    ++            else {
    ++                var values = {};
    ++                for (let item of list) {
    ++                    if ( item.name === 'id' ) {
    ++                        values.index = self.nodes.findIndex(x => x.id == item.value);
     +                    }
    -+                    self.TruncateLabel(elem);
    -+                    p_el.select("foreignObject").remove();
    -+                    self.Deselect();
    -+                    self.Refresh();
    -+                })
    -+                .on("keypress", function(d) {
    -+                    // IE fix
    -+                    if (!d3.event)
    -+                        d3.event = window.event;
    -+
    -+                    var e = d3.event;
    -+                    if (e.keyCode == 13)
    -+                    {
    -+                        if (typeof(e.cancelBubble) !== 'undefined') // IE
    -+                            e.cancelBubble = true;
    -+                        if (e.stopPropagation)
    -+                            e.stopPropagation();
    -+                        e.preventDefault();
    -+
    -+                        var text = input.node().value;
    -+                        if ( text && text != original_value ) {
    -+                            self.UpdateNodeStatus(self.nodes[index], text);
    -+                            el.text(text);
    -+                        }
    -+                        else {
    -+                            el.text(original_value);
    -+                        }
    -+                        self.TruncateLabel(elem);
    -+                        p_el.select("foreignObject").remove();
    -+                        self.Deselect();
    -+                        self.Refresh();
    -+                    }
    -+                });
    ++                    values[item.name] = item.value;
    ++                }
    ++                self.UpdateNodeModel(self.nodes[values.index], values);
    ++                self.ExportAsConfiguration();
    ++                self.Refresh();
    ++            }
    ++            nodeInput.toggle();
     +        }
     +
     +        RenderLink() {
    @@ -1761,6 +1881,12 @@
     +        }
     +
     +        Deselect() {
    ++            if ( jQuery("#lifeycycle-ui-edit-node").is(':visible') ) {
    ++                jQuery("#lifeycycle-ui-edit-node").toggle();
    ++            }
    ++
    ++            this.editing_node = null;
    ++
     +            if (!this.selected_node) return;
     +            this.selected_node = null;
     +
    @@ -1814,7 +1940,6 @@
     +        }
     +    }
     +});
    -\ No newline at end of file
     
     diff --git a/share/static/js/lifecycleui-model.js b/share/static/js/lifecycleui-model.js
     new file mode 100644
    @@ -1936,14 +2061,14 @@
     +        });
     +    }
     +
    -+    UpdateNodeStatus(node, text) {
    ++    UpdateNodeModel(node, args) {
     +        var nodeIndex = this.nodes.findIndex(x => x.id == node.id);
     +
    -+        this.nodes[nodeIndex] = {...this.nodes[nodeIndex], name: text};
    ++        this.nodes[nodeIndex] = {...this.nodes[nodeIndex], ...args};
     +        var nodeUpdated = this.nodes[nodeIndex];
     +
     +        // Update any links with node being changed as source
    -+        var links    = this.links.filter(function(l) { return (
    ++        var links = this.links.filter(function(l) { return (
     +            ( l.source.id === node.id ) )
     +        });
     +        links.forEach(link => {
    @@ -1952,7 +2077,7 @@
     +        });
     +
     +        // Update any links with node being changed as target
    -+        var links    = this.links.filter(function(l) { return (
    ++        var links = this.links.filter(function(l) { return (
     +            ( l.target.id === node.id ) )
     +        });
     +        links.forEach(link => {
    @@ -1981,6 +2106,7 @@
     +        // Grab our links
     +        config.transitions[""] = self.config.transitions ? self.config.transitions[""]: [];
     +
    ++        var seen = {};
     +        self.nodes.forEach(source => {
     +            var links = self.LinksForNode(source);
     +            var targets = links.map(link => {
    @@ -1992,596 +2118,17 @@
     +                }
     +            });
     +            config.transitions[source.name] = targets;
    ++            seen[source.name] = 1;
     +        });
    ++
    ++        for (let transition in config.transitions) {
    ++            if( !seen[transition] ) {
    ++                delete config.transitions[transition];
    ++            }
    ++        }
     +        self.config = config;
     +
     +        var field = jQuery('input[name="Config"]');
     +        field.val(JSON.stringify(self.config));
     +    };
     +}
    -
    -diff --git a/share/static/js/lifecycleui-viewer-interactive.js b/share/static/js/lifecycleui-viewer-interactive.js
    -new file mode 100644
    ---- /dev/null
    -+++ b/share/static/js/lifecycleui-viewer-interactive.js
    -@@
    -+jQuery(function () {
    -+    var Super = RT.LifecycleViewer;
    -+
    -+    function Interactive (container) {
    -+        Super.call(this);
    -+    };
    -+    Interactive.prototype = Object.create(Super.prototype);
    -+
    -+    Interactive.prototype.deselectStatus = function () {
    -+        delete this.selectedStatus;
    -+        delete this.selectedMenu;
    -+
    -+        this.statusContainer.selectAll('.selected').classed('selected', false);
    -+        this.menuContainer.find('.status-menu.selected').removeClass('selected');
    -+    };
    -+
    -+    Interactive.prototype._setMenuPosition = function () {
    -+        if (!this.selectedStatus) {
    -+            return;
    -+        }
    -+
    -+        var d = this.selectedStatus;
    -+        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;
    -+
    -+        this.selectedMenu.css({top: y, left: x});
    -+    };
    -+
    -+    Interactive.prototype.clickedStatus = function (d) {
    -+        var statusName = d.name;
    -+        this.selectedMenu = this.menuContainer.find('.status-menu[data-status="'+statusName+'"]');
    -+        this.selectedStatus = d;
    -+        var statusNode = this.statusContainer.select('g[data-key="'+ d._key + '"]');
    -+
    -+        this.statusContainer.selectAll('.selected').classed('selected', false);
    -+        statusNode.classed('selected', true);
    -+
    -+        this.menuContainer.find('.status-menu.selected').removeClass('selected');
    -+        this.selectedMenu.addClass('selected');
    -+
    -+        this.selectedMenu.find(".toplevel").addClass('sf-menu sf-vertical sf-js-enabled sf-shadow').supersubs().superfish({ speed: 'fast' });
    -+
    -+        this._setMenuPosition();
    -+    };
    -+
    -+    Interactive.prototype.didZoom = function () {
    -+        Super.prototype.didZoom.call(this);
    -+        if (this.selectedMenu) {
    -+            this._setMenuPosition();
    -+            var svgBox = this.svg.node().getBoundingClientRect();
    -+            var menuBox = this.selectedMenu[0].getBoundingClientRect();
    -+
    -+            var overlap = !(svgBox.right  < menuBox.left ||
    -+                            svgBox.left   > menuBox.right ||
    -+                            svgBox.bottom < menuBox.top ||
    -+                            svgBox.top    > menuBox.bottom);
    -+            if (!overlap) {
    -+                this.deselectStatus();
    -+            }
    -+        }
    -+    };
    -+
    -+    Interactive.prototype.initializeViewer = function (node, name, config, focusStatus) {
    -+         var self = this;
    -+         Super.prototype.initializeViewer.call(self, node, name, config, focusStatus);
    -+         self.menuContainer = jQuery(node).find('.status-menus');
    -+         self.svg.on('click', function () { self.deselectStatus() });
    -+
    -+         // copy classes from <a> to <li> for improved styling
    -+         self.menuContainer.find('.status-menu li a').each(function () {
    -+             var link = jQuery(this);
    -+             var item = link.closest('li');
    -+             item.addClass(link.attr("class"));
    -+             item.removeClass('menu-item');
    -+         });
    -+    };
    -+
    -+    RT.LifecycleViewerInteractive = Interactive;
    -+});
    -+
    -
    -diff --git a/share/static/js/lifecycleui-viewer.js b/share/static/js/lifecycleui-viewer.js
    -new file mode 100644
    ---- /dev/null
    -+++ b/share/static/js/lifecycleui-viewer.js
    -@@
    -+jQuery(function () {
    -+    class Viewer {
    -+        constructor(container) {
    -+            this.width = 809;
    -+            this.height = 500;
    -+            this.statusCircleRadius = 35;
    -+            this.statusCircleRadiusFudge = 4; // required to give room for the arrowhead
    -+            this.gridSize = 10;
    -+            this.padding = this.statusCircleRadius * 2;
    -+            this.animationFactor = 1; // bump this to 10 debug JS animations
    -+        }
    -+        createScale(size, padding) {
    -+            return d3.scaleLinear()
    -+                .domain([0, 10000])
    -+                .range([padding, size - padding]);
    -+        }
    -+        gridScale(v) { return Math.round(v / this.gridSize) * this.gridSize; }
    -+        xScale(x) { return this.gridScale(this._xScale(x)); }
    -+        yScale(y) { return this.gridScale(this._yScale(y)); }
    -+        xScaleZero(x) { return this.gridScale(this._xScaleZero(x)); }
    -+        yScaleZero(y) { return this.gridScale(this._yScaleZero(y)); }
    -+        xScaleInvert(x) { return Math.floor(this._xScale.invert(x)); }
    -+        yScaleInvert(y) { return Math.floor(this._yScale.invert(y)); }
    -+        xScaleZeroInvert(x) { return Math.floor(this._xScaleZero.invert(x)); }
    -+        yScaleZeroInvert(y) { return Math.floor(this._yScaleZero.invert(y)); }
    -+        addZoomBehavior() {
    -+            var self = this;
    -+            self._zoom = d3.zoom()
    -+                .scaleExtent([.3, 2])
    -+                .on("zoom", function () {
    -+                    if (self.zoomControl) {
    -+                        self.didZoom();
    -+                    }
    -+                });
    -+            self.svg.call(self._zoom);
    -+        }
    -+        didZoom() {
    -+            this._currentZoom = d3.event.transform;
    -+            this.transformContainer.attr("transform", d3.event.transform);
    -+        }
    -+        zoomScale(scaleBy, animated) {
    -+            if (animated) {
    -+                this.svg.transition()
    -+                    .duration(350 * this.animationFactor)
    -+                    .call(this._zoom.scaleBy, scaleBy);
    -+            }
    -+            else {
    -+                this.svg.call(this._zoom.scaleBy, scaleBy);
    -+            }
    -+        }
    -+        _setZoom(zoom, animated) {
    -+            if (animated) {
    -+                this.svg.transition()
    -+                    .duration(750 * this.animationFactor)
    -+                    .call(this._zoom.transform, zoom);
    -+            }
    -+            else {
    -+                this.svg.call(this._zoom.transform, zoom);
    -+            }
    -+        }
    -+        resetZoom(animated) {
    -+            this._setZoom(this._zoomIdentity, animated);
    -+        }
    -+        zoomToFit(animated) {
    -+            var bounds = this.transformContainer.node().getBBox();
    -+            var parent = this.transformContainer.node().parentElement;
    -+            var fullWidth = parent.clientWidth || parent.parentNode.clientWidth, fullHeight = parent.clientHeight || parent.parentNode.clientHeight;
    -+            var width = bounds.width, height = bounds.height;
    -+            var midX = bounds.x + width / 2, midY = bounds.y + height / 2;
    -+            var scale = .9 / Math.max(width / fullWidth, height / fullHeight);
    -+            var tx = fullWidth / 2 - scale * midX;
    -+            var ty = fullHeight / 2 - scale * midY;
    -+            this._setZoom(d3.zoomIdentity.translate(tx, ty).scale(scale), animated);
    -+        }
    -+        didEnterStatusNodes(statuses) { }
    -+        didEnterTransitions(paths) { }
    -+        didEnterTextDecorations(labels) { }
    -+        didEnterPolygonDecorations(polygons) { }
    -+        didEnterCircleDecorations(circles) { }
    -+        didEnterLineDecorations(lines) { }
    -+        renderStatusNodes(initial) {
    -+            var self = this;
    -+            var statuses = self.statusContainer.selectAll("g")
    -+                .data(self.lifecycle.statusObjects(), function (d) { return d._key; });
    -+            var exitStatuses = statuses.exit()
    -+                .classed("removing", true)
    -+                .transition().duration(200 * self.animationFactor)
    -+                .remove();
    -+            exitStatuses.select('circle')
    -+                .attr("r", self.statusCircleRadius * .8);
    -+            var newStatuses = statuses.enter().append("g")
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .attr("id", function (d) { return 'key-'+d._key; })
    -+                .call(function (statuses) { self.didEnterStatusNodes(statuses); });
    -+            newStatuses.append("circle")
    -+                .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8)
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+
    -+                    self.focusItem(d)
    -+                })
    -+            newStatuses.append("text")
    -+                .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8)
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedStatus(d);
    -+                })
    -+            if (!initial) {
    -+                newStatuses.transition().duration(200 * self.animationFactor)
    -+                    .select("circle")
    -+                    .attr("r", self.statusCircleRadius);
    -+            }
    -+            var allStatuses = newStatuses.merge(statuses)
    -+                .classed("focus", function (d) { return self.isFocused(d); })
    -+                .classed("focus-from", function (d) { return self.isFocusedTransition(d, true); })
    -+                .classed("focus-to", function (d) { return self.isFocusedTransition(d, false); });
    -+            allStatuses.select("circle")
    -+                .attr("cx", function (d) { return self.xScale(d.x); })
    -+                .attr("cy", function (d) { return self.yScale(d.y); })
    -+                .attr("fill", function (d) { return d.color; });
    -+            allStatuses.select("text")
    -+                .attr("x", function (d) { return self.xScale(d.x); })
    -+                .attr("y", function (d) { return self.yScale(d.y); })
    -+                .attr("fill", function (d) { return d3.hsl(d.color).l > 0.35 ? '#000' : '#fff'; })
    -+                .text(function (d) { return d.name; }).each(function () { self.truncateLabel(this); });
    -+        }
    -+        clickedStatus(d) { }
    -+        clickedTransition(d) { }
    -+        clickedDecoration(d) { }
    -+        truncateLabel(element) {
    -+            var node = d3.select(element), textLength = node.node().getComputedTextLength(), text = node.text();
    -+            while (textLength > this.statusCircleRadius * 1.8 && text.length > 0) {
    -+                text = text.slice(0, -1);
    -+                node.text(text + '…');
    -+                textLength = node.node().getComputedTextLength();
    -+            }
    -+        }
    -+        transitionArc(d) {
    -+            // c* variables are circle centers
    -+            // a* variables are for the arc path which is from circle edge to circle edge
    -+            var from = this.lifecycle.statusObjectForName(d.from), to = this.lifecycle.statusObjectForName(d.to), cx0 = this.xScale(from.x), cx1 = this.xScale(to.x), cy0 = this.yScale(from.y), cy1 = this.yScale(to.y), cdx = cx1 - cx0, cdy = cy1 - cy0;
    -+            // the circles on top of each other would calculate atan2(0,0) which is
    -+            // undefined and a little nonsensical
    -+            if (cdx == 0 && cdy == 0) {
    -+                return null;
    -+            }
    -+            var theta = Math.atan2(cdy, cdx), r = this.statusCircleRadius, ax0 = cx0 + r * Math.cos(theta), ay0 = cy0 + r * Math.sin(theta), ax1 = cx1 - (r + this.statusCircleRadiusFudge) * Math.cos(theta), ay1 = cy1 - (r + this.statusCircleRadiusFudge) * Math.sin(theta), dr = Math.abs((ax1 - ax0) * 4) + Math.abs((ay1 - ay0) * 4);
    -+            return "M" + ax0 + "," + ay0 + " A" + dr + "," + dr + " 0 0,1 " + ax1 + "," + ay1;
    -+        }
    -+        renderTransitions(initial) {
    -+            var self = this;
    -+            var paths = self.transitionContainer.selectAll("path")
    -+                .data(self.lifecycle.transitions, function (d) { return d._key; });
    -+            paths.exit().classed("removing", true)
    -+                .each(function (d) {
    -+                    var length = this.getTotalLength();
    -+                    var path = d3.select(this);
    -+                    path.attr("stroke-dasharray", length + " " + length)
    -+                        .attr("stroke-dashoffset", 0)
    -+                        .style("marker-end", "none")
    -+                        .transition().duration(200 * self.animationFactor).ease(d3.easeLinear)
    -+                        .attr("stroke-dashoffset", length)
    -+                        .remove();
    -+                });
    -+            var newPaths = paths.enter().append("path")
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedTransition(d);
    -+                })
    -+                .call(function (paths) { self.didEnterTransitions(paths); });
    -+            newPaths.merge(paths)
    -+                .attr("d", function (d) { return self.transitionArc(d); })
    -+                .classed("dashed", function (d) { return d.style == 'dashed'; })
    -+                .classed("dotted", function (d) { return d.style == 'dotted'; })
    -+                .classed("focus", function (d) { return self.isFocused(d); })
    -+                .classed("focus-from", function (d) { return self.isFocusedTransition(d, true); })
    -+                .classed("focus-to", function (d) { return self.isFocusedTransition(d, false); });
    -+            if (!initial) {
    -+                newPaths.each(function (d) {
    -+                    var length = this.getTotalLength();
    -+                    var path = d3.select(this);
    -+                    path.attr("stroke-dasharray", length + " " + length)
    -+                        .attr("stroke-dashoffset", length)
    -+                        .style("marker-end", "none")
    -+                        .transition().duration(200 * self.animationFactor).ease(d3.easeLinear)
    -+                        .attr("stroke-dashoffset", 0)
    -+                        .on("end", function () {
    -+                            d3.select(this)
    -+                                .attr("stroke-dasharray", undefined)
    -+                                .attr("stroke-offset", undefined)
    -+                                .style("marker-end", undefined);
    -+                        });
    -+                });
    -+            }
    -+        }
    -+        _wrapTextDecoration(node, text) {
    -+            if (node.attr('data-text') == text) {
    -+                return;
    -+            }
    -+            var lines = text.split(/\n/), lineHeight = 1.1;
    -+            if (node.attr('data-text')) {
    -+                node.selectAll("*").remove();
    -+            }
    -+            node.attr('data-text', text);
    -+            for (var i = 0; i < lines.length; ++i) {
    -+                node.append("tspan").attr("dy", (i + 1) * lineHeight + "em").text(lines[i]);
    -+            }
    -+        }
    -+        renderTextDecorations(initial) {
    -+            var self = this;
    -+            var labels = self.decorationContainer.selectAll("text")
    -+                .data(self.lifecycle.decorations.text, function (d) { return d._key; });
    -+            labels.exit()
    -+                .classed("removing", true)
    -+                .transition().duration(200 * self.animationFactor)
    -+                .remove();
    -+            var newLabels = labels.enter().append("text")
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedDecoration(d);
    -+                })
    -+                .call(function (labels) { self.didEnterTextDecorations(labels); });
    -+            if (!initial) {
    -+                newLabels.style("opacity", 0.15)
    -+                    .transition().duration(200 * self.animationFactor)
    -+                    .style("opacity", 1)
    -+                    .on("end", function () { d3.select(this).style("opacity", undefined); });
    -+            }
    -+            newLabels.merge(labels)
    -+                .attr("x", function (d) { return self.xScale(d.x); })
    -+                .attr("y", function (d) { return self.yScale(d.y); })
    -+                .classed("bold", function (d) { return d.bold; })
    -+                .classed("italic", function (d) { return d.italic; })
    -+                .classed("focus", function (d) { return self.isFocused(d); })
    -+                .each(function (d) { self._wrapTextDecoration(d3.select(this), d.text); })
    -+                .selectAll("tspan")
    -+                .attr("x", function (d) { return self.xScale(d.x); })
    -+                .attr("y", function (d) { return self.yScale(d.y); });
    -+        }
    -+        renderPolygonDecorations(initial) {
    -+            var self = this;
    -+            var polygons = self.decorationContainer.selectAll("polygon")
    -+                .data(self.lifecycle.decorations.polygon, function (d) { return d._key; });
    -+            polygons.exit()
    -+                .classed("removing", true)
    -+                .transition().duration(200 * self.animationFactor)
    -+                .remove();
    -+            var newPolygons = polygons.enter().append("polygon")
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedDecoration(d);
    -+                })
    -+                .call(function (polygons) { self.didEnterPolygonDecorations(polygons); });
    -+            if (!initial) {
    -+                newPolygons.style("opacity", 0.15)
    -+                    .transition().duration(200 * self.animationFactor)
    -+                    .style("opacity", 1)
    -+                    .on("end", function () { d3.select(this).style("opacity", undefined); });
    -+            }
    -+            newPolygons.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) {
    -+                    return jQuery.map(d.points, function (p) {
    -+                        return [self.xScaleZero(p.x), self.yScaleZero(p.y)].join(",");
    -+                    }).join(" ");
    -+                })
    -+                .classed("focus", function (d) { return self.isFocused(d); });
    -+        }
    -+        renderCircleDecorations(initial) {
    -+            var self = this;
    -+            var circles = self.decorationContainer.selectAll("circle.decoration")
    -+                .data(self.lifecycle.decorations.circle, function (d) { return d._key; });
    -+            circles.exit()
    -+                .classed("removing", true)
    -+                .transition().duration(200 * self.animationFactor)
    -+                .remove();
    -+            var newCircles = circles.enter().append("circle")
    -+                .classed("decoration", true)
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedDecoration(d);
    -+                })
    -+                .call(function (circles) { self.didEnterCircleDecorations(circles); });
    -+            if (!initial) {
    -+                newCircles.style("opacity", 0.15)
    -+                    .transition().duration(200 * self.animationFactor)
    -+                    .style("opacity", 1)
    -+                    .on("end", function () { d3.select(this).style("opacity", undefined); });
    -+            }
    -+            newCircles.merge(circles)
    -+                .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("cx", function (d) { return self.xScale(d.x); })
    -+                .attr("cy", function (d) { return self.yScale(d.y); })
    -+                .attr("r", function (d) { return d.r; })
    -+                .classed("focus", function (d) { return self.isFocused(d); });
    -+        }
    -+        renderLineDecorations(initial) {
    -+            var self = this;
    -+            var lines = self.decorationContainer.selectAll("line")
    -+                .data(self.lifecycle.decorations.line, function (d) { return d._key; });
    -+            lines.exit()
    -+                .classed("removing", true)
    -+                .transition().duration(200 * self.animationFactor)
    -+                .remove();
    -+            var newLines = lines.enter().append("line")
    -+                .attr("data-key", function (d) { return d._key; })
    -+                .on("click", function (d) {
    -+                    d3.event.stopPropagation();
    -+                    self.clickedDecoration(d);
    -+                })
    -+                .call(function (lines) { self.didEnterLineDecorations(lines); });
    -+            if (!initial) {
    -+                newLines.each(function (d) {
    -+                    var length = Math.sqrt((d.points[1].x - d.points[0].x) ** 2 + (d.points[1].y - d.points[0].y) ** 2);
    -+                    var path = d3.select(this);
    -+                    path.attr("stroke-dasharray", length + " " + length)
    -+                        .attr("stroke-dashoffset", length)
    -+                        .style("marker-start", "none")
    -+                        .style("marker-end", "none")
    -+                        .transition().duration(200 * self.animationFactor).ease(d3.easeLinear)
    -+                        .attr("stroke-dashoffset", 0)
    -+                        .on("end", function () {
    -+                            d3.select(this)
    -+                                .attr("stroke-dasharray", undefined)
    -+                                .attr("stroke-offset", undefined)
    -+                                .style("marker-start", undefined)
    -+                                .style("marker-end", undefined);
    -+                        });
    -+                });
    -+            }
    -+            newLines.merge(lines)
    -+                .classed("dashed", function (d) { return d.style == 'dashed'; })
    -+                .classed("dotted", function (d) { return d.style == 'dotted'; })
    -+                .attr("transform", function (d) { return "translate(" + self.xScale(d.x) + ", " + self.yScale(d.y) + ")"; })
    -+                .attr("x1", function (d) { return self.xScaleZero(d.points[0].x); })
    -+                .attr("y1", function (d) { return self.yScaleZero(d.points[0].y); })
    -+                .attr("x2", function (d) { return self.xScaleZero(d.points[1].x); })
    -+                .attr("y2", function (d) { return self.yScaleZero(d.points[1].y); })
    -+                .classed("focus", function (d) { return self.isFocused(d); })
    -+                .attr("marker-start", function (d) { return d.startMarker == 'none' ? undefined : "url(#line_marker_" + d.startMarker + ")"; })
    -+                .attr("marker-end", function (d) { return d.endMarker == 'none' ? undefined : "url(#line_marker_" + d.endMarker + ")"; });
    -+        }
    -+        renderDecorations(initial) {
    -+            this.renderPolygonDecorations(initial);
    -+            this.renderCircleDecorations(initial);
    -+            this.renderLineDecorations(initial);
    -+            this.renderTextDecorations(initial);
    -+        }
    -+        renderDisplay(initial) {
    -+            this.renderTransitions(initial);
    -+            this.renderStatusNodes(initial);
    -+            this.renderDecorations(initial);
    -+        }
    -+        centerOnItem(item, animated) {
    -+            var rect = this.svg.node().getBoundingClientRect();
    -+            var scale = this._zoomIdentityScale;
    -+            var x = rect.width / 2 - this.xScale(item.x) * scale;
    -+            var y = rect.height / 2 - this.yScale(item.y) * scale;
    -+            this._zoomIdentity = d3.zoomIdentity.translate(x, y).scale(this._zoomIdentityScale);
    -+            this.resetZoom(animated);
    -+        }
    -+        defocus() {
    -+            this._focusItem = null;
    -+            this.svg.classed("has-focus", false)
    -+                .attr('data-focus-type', undefined);
    -+        }
    -+        focusItem(d) {
    -+            this.defocus();
    -+            this._focusItem = d;
    -+            this.svg.classed("has-focus", true)
    -+                .attr('data-focus-type', d._type);
    -+        }
    -+        focusOnStatus(statusName, center, animated) {
    -+            if (!statusName) {
    -+                return;
    -+            }
    -+            var meta = this.lifecycle.statusObjectForName(statusName);
    -+            this.focusItem(meta);
    -+            if (center) {
    -+                this.centerOnItem(meta, animated);
    -+            }
    -+        }
    -+        isFocused(d) {
    -+            if (!this._focusItem) {
    -+                return false;
    -+            }
    -+            return this._focusItem._key == d._key;
    -+        }
    -+        isFocusedTransition(d, isFrom) {
    -+            if (!this._focusItem) {
    -+                return false;
    -+            }
    -+            if (d._type == 'status') {
    -+                if (this._focusItem._type == 'status') {
    -+                    if (isFrom) {
    -+                        return this.lifecycle.hasTransition(d.name, this._focusItem.name);
    -+                    }
    -+                    else {
    -+                        return this.lifecycle.hasTransition(this._focusItem.name, d.name);
    -+                    }
    -+                }
    -+                else if (this._focusItem._type == 'transition') {
    -+                    if (isFrom) {
    -+                        return this._focusItem.from == d.name;
    -+                    }
    -+                    else {
    -+                        return this._focusItem.to == d.name;
    -+                    }
    -+                }
    -+            }
    -+            else if (d._type == 'transition') {
    -+                if (this._focusItem._type == 'status') {
    -+                    if (isFrom) {
    -+                        return d.to == this._focusItem.name;
    -+                    }
    -+                    else {
    -+                        return d.from == this._focusItem.name;
    -+                    }
    -+                }
    -+            }
    -+            return false;
    -+        }
    -+
    -+        initializeViewer(node, name, config, focusStatus) {
    -+            var self = this;
    -+            self.container = jQuery(node);
    -+            self.svg = d3.select(node).select('svg');
    -+            self.transformContainer = self.svg.select('g.transform');
    -+            self.transitionContainer = self.svg.select('g.transitions');
    -+            self.statusContainer = self.svg.select('g.statuses');
    -+            self.decorationContainer = self.svg.select('g.decorations');
    -+            self._xScale = self.createScale(self.width, self.padding);
    -+            self._yScale = self.createScale(self.height, self.padding);
    -+            self._xScaleZero = self.createScale(self.width, 0);
    -+            self._yScaleZero = self.createScale(self.height, 0);
    -+            // zoom in a bit, but not too much
    -+            var scale = self.svg.node().getBoundingClientRect().width / self.width;
    -+            scale = scale ** .6;
    -+            self._zoomIdentityScale = scale;
    -+            self._zoomIdentity = self._currentZoom = d3.zoomIdentity.scale(self._zoomIdentityScale);
    -+            RT.Lifecycle.name = name;
    -+            self.lifecycle = RT.Lifecycle;
    -+            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')) {
    -+                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._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);
    -+            });
    -+            self.container.on('click', 'button.zoom-out', function (e) {
    -+                e.preventDefault();
    -+                self.zoomScale(.75, true);
    -+            });
    -+            self.container.on('click', 'button.zoom-reset', function (e) {
    -+                e.preventDefault();
    -+                self.resetZoom(true);
    -+            });
    -+        }
    -+    };
    -+
    -+    RT.LifecycleViewer = Viewer;
    -+});
    -+
    -
2: db4a3b570b < -:  ------- Temp



More information about the rt-commit mailing list