[Rt-commit] rt branch, 4.6/lifecycle-ui-cleanup, created. rt-4.4.1-269-g6ac265b75
Craig Kaiser
craig at bestpractical.com
Fri Oct 18 17:32:18 EDT 2019
The branch, 4.6/lifecycle-ui-cleanup has been created
at 6ac265b756942c18ea553ed8684904af74125842 (commit)
- Log -----------------------------------------------------------------
commit e04fde3376de1cec418d26dda6057df25cff9069
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 09:34:09 2019 -0400
Signal lifecycle cache needs update using system LifecycleCacheNeedsUpdate method
Lifecycle can now be edited in the web UI, meaning that all RT threads need to
know when the cache is in need of update.
diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 784b38453..073b7526a 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -57,6 +57,8 @@ our %LIFECYCLES;
our %LIFECYCLES_CACHE;
our %LIFECYCLES_TYPES;
+my $lifecycle_cache_time = 0;
+
# cache structure:
# {
# lifecycle_x => {
@@ -109,7 +111,7 @@ sub new {
my $proto = shift;
my $self = bless {}, ref($proto) || $proto;
- $self->FillCache unless keys %LIFECYCLES_CACHE;
+ RT->System->LifecycleCacheNeedsUpdate(1);
return $self;
}
@@ -144,13 +146,19 @@ sub Load {
@_,
);
+ my $needs_update = RT->System->LifecycleCacheNeedsUpdate;
+ if ($needs_update > $lifecycle_cache_time) {
+ $self->FillCache();
+ $lifecycle_cache_time = $needs_update;
+ }
+
if (defined $args{Name} and exists $LIFECYCLES_CACHE{ $args{Name} }) {
$self->{'name'} = $args{Name};
$self->{'data'} = $LIFECYCLES_CACHE{ $args{Name} };
$self->{'type'} = $args{Type};
my $found_type = $self->{'data'}{'type'};
- warn "Found type of $found_type ne $args{Type}" if $found_type ne $args{Type};
+ warn "Found type of $found_type ne ".$args{'Type'} if $found_type ne $args{Type};
} elsif (not $args{Name} and exists $LIFECYCLES_TYPES{ $args{Type} }) {
$self->{'data'} = $LIFECYCLES_TYPES{ $args{Type} };
$self->{'type'} = $args{Type};
@@ -198,8 +206,6 @@ sub ListAll {
my $self = shift;
my $for = shift || 'ticket';
- $self->FillCache unless keys %LIFECYCLES_CACHE;
-
return sort grep {$LIFECYCLES_CACHE{$_}{type} eq $for}
grep $_ ne '__maps__', keys %LIFECYCLES_CACHE;
}
@@ -468,8 +474,6 @@ sub RightsDescription {
my $self = shift;
my $type = shift;
- $self->FillCache unless keys %LIFECYCLES_CACHE;
-
my %tmp;
foreach my $lifecycle ( values %LIFECYCLES_CACHE ) {
next unless exists $lifecycle->{'rights'};
@@ -519,8 +523,6 @@ sub Actions {
my $from = shift || return ();
$from = lc $from;
- $self->FillCache unless keys %LIFECYCLES_CACHE;
-
my @res = grep lc $_->{'from'} eq $from || ( $_->{'from'} eq '*' && lc $_->{'to'} ne $from ),
@{ $self->{'data'}{'actions'} };
@@ -595,7 +597,6 @@ that require translation.
sub ForLocalization {
my $self = shift;
- $self->FillCache unless keys %LIFECYCLES_CACHE;
my @res = ();
@@ -789,6 +790,8 @@ sub FillCache {
and $class->can("RegisterRights");
}
+ $lifecycle_cache_time = time;
+
return;
}
@@ -839,7 +842,7 @@ sub _SaveLifecycles {
return ($ok, $msg) if !$ok;
}
- RT::Lifecycle->FillCache;
+ RT->System->LifecycleCacheNeedsUpdate(1);
return 1;
}
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index 08d79dde4..68a7f817d 100644
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -256,6 +256,27 @@ sub ConfigCacheNeedsUpdate {
}
}
+=head2 LifecycleCacheNeedsUpdate ( 1 )
+
+Attribute to decide when we need to flush the list of lifecycles
+and re-register any changes. This is needed for the lifecycle UI editor.
+
+If passed a true value, will update the attribute to be the current time.
+
+=cut
+
+sub LifecycleCacheNeedsUpdate {
+ my $self = shift;
+ my $update = shift;
+
+ if ($update) {
+ return $self->SetAttribute(Name => 'LifecycleCacheNeedsUpdate', Content => time);
+ } else {
+ my $cache = $self->FirstAttribute('LifecycleCacheNeedsUpdate');
+ return (defined $cache ? $cache->Content : 0 );
+ }
+}
+
=head2 AddUpgradeHistory package, data
Adds an entry to the upgrade history database. The package can be either C<RT>
commit 5d1f486e1a74b4084cad92e4f705edf7e0e1a223
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 09:44:40 2019 -0400
Show 'select' and 'create' page menu options for lifecycle pages
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index e3baabedd..9556e3f1d 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -1156,6 +1156,13 @@ sub _BuildAdminMenu {
RT::Interface::Web::EscapeURI(\$Name_uri);
RT::Interface::Web::EscapeURI(\$Type_uri);
+ my $lifecycles = $page->child( lifecycles =>
+ title => loc('Lifecycles'),
+ path => '/Admin/Lifecycles/',
+ );
+ $lifecycles->child( select => title => loc('Select'), path => '/Admin/Lifecycles/');
+ $lifecycles->child( create => title => loc('Create'), path => '/Admin/Lifecycles/Create.html');
+
$page->child( basics => title => loc('Modify'), path => "/Admin/Lifecycles/Modify.html?Type=" . $Type_uri . "&Name=" . $Name_uri );
$page->child( mappings => title => loc('Mappings'), path => "/Admin/Lifecycles/Mappings.html?Type=" . $Type_uri . "&Name=" . $Name_uri );
}
commit 1b671fc786ec025331568337d781e14d6232abff
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 10:24:59 2019 -0400
Make sure default value for lifecycle type is 'ticket'
diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 073b7526a..bec9fb72d 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -145,6 +145,7 @@ sub Load {
Name => '',
@_,
);
+ $args{'Type'} = $args{'Type'} // 'ticket';
my $needs_update = RT->System->LifecycleCacheNeedsUpdate;
if ($needs_update > $lifecycle_cache_time) {
commit 9f8cedda33ab54e77d5b14bf6ff856994d731a54
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 10:47:04 2019 -0400
Add message when no lifecycle mappings available
diff --git a/share/html/Admin/Lifecycles/Mappings.html b/share/html/Admin/Lifecycles/Mappings.html
index 891936d79..c587293cd 100644
--- a/share/html/Admin/Lifecycles/Mappings.html
+++ b/share/html/Admin/Lifecycles/Mappings.html
@@ -83,6 +83,10 @@
</&>
% }
+% unless ( scalar @lifecycles ) {
+ <p><&|/l&>Mapping only available when more than one lifecycle exists</&></p>
+% }
+
<& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
</form>
commit 28653f03e99cc1c79af3d4ce3ab4c53bfdd0b340
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 11:55:34 2019 -0400
Remove checkered background from lifecycle editor graph
diff --git a/share/static/css/base/lifecycleui-editor.css b/share/static/css/base/lifecycleui-editor.css
index 7e83c6852..bd168915d 100644
--- a/share/static/css/base/lifecycleui-editor.css
+++ b/share/static/css/base/lifecycleui-editor.css
@@ -4,12 +4,6 @@
width: 809px;
height: 500px;
- /* checkerboard pattern */
- background: #F9F9F9 url('data:image/svg+xml,\
- <svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" fill-opacity=".05" >\
- <rect x="200" width="200" height="200" />\
- <rect y="200" width="200" height="200" />\
- </svg>');
background-size: 25px 25px;
}
commit 2d03d605bc3a2c201e4940e7b560a64829a074c1
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Sep 27 12:10:39 2019 -0400
Center lifecycle editor
diff --git a/share/static/css/base/lifecycleui-editor.css b/share/static/css/base/lifecycleui-editor.css
index bd168915d..732a82043 100644
--- a/share/static/css/base/lifecycleui-editor.css
+++ b/share/static/css/base/lifecycleui-editor.css
@@ -1,19 +1,19 @@
+.lifecycle-ui {
+ margin-left: 10%;
+}
+
.lifecycle-ui.editing svg {
- display: inline-block;
float: left;
- width: 809px;
+ width: 60%;
height: 500px;
-
- background-size: 25px 25px;
}
.lifecycle-ui.editing .overlay-buttons {
- left: 700px;
+ left: 50%;
}
.lifecycle-ui .inspector {
display: inline-block;
- width: 250px;
min-height: 500px;
border: 1px solid black;
}
commit 7a3040e906e258e0fd9c4922b4e3e1c7778cdd0c
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Oct 18 08:27:46 2019 -0400
Get text input box on status node click
Have a text input box appear that allows editing the current status
name. Clicking away or hitting the enter key should apply these changes
to the status name. Saving the updates saves the changes to the RT database
lifecycle.
diff --git a/share/static/js/lifecycleui-editor.js b/share/static/js/lifecycleui-editor.js
index c1f551bd9..297d80c33 100644
--- a/share/static/js/lifecycleui-editor.js
+++ b/share/static/js/lifecycleui-editor.js
@@ -1,676 +1,245 @@
jQuery(function () {
- var Super = RT.LifecycleViewer;
- function Editor (container) {
- Super.call(this);
- this.pointHandleRadius = 5;
- };
- Editor.prototype = Object.create(Super.prototype);
-
- Editor.prototype._initializeTemplates = function (container) {
- var self = this;
-
- Handlebars.registerHelper('select', function(value, options) {
- var node = jQuery('<select />').html( options.fn(this) );
- node.find('[value="' + value + '"]').attr({'selected':'selected'});
- return node.html();
- });
-
- Handlebars.registerHelper('canAddTransition', function(fromStatus, toStatus, lifecycle) {
- if (fromStatus == toStatus) {
- return false;
- }
- return !lifecycle.hasTransition(fromStatus, toStatus);
- });
-
- Handlebars.registerHelper('canSelectTransition', function(fromStatus, toStatus, lifecycle) {
- return lifecycle.hasTransition(fromStatus, toStatus);
- });
-
- Handlebars.registerHelper('selectedRights', function(lifecycle) {
- return lifecycle.selectedRights();
- });
-
- Handlebars.registerHelper('truncate', function(text) {
- if (text.length > 15) {
- text = text.substr(0, 15) + '…';
- }
- return text;
- });
-
- var templates = {};
- self.container.find('script.lifecycle-inspector-template').each(function () {
- var type = jQuery(this).data('type');
- var template = jQuery(this).html();
- var fn = Handlebars.compile(template);
- templates[type] = fn;
- Handlebars.registerPartial('lifecycleui_' + type, fn);
- });
- return templates;
- };
+ RT.Editor = class LifecycleEditor extends RT.LifecycleViewer {
+ constructor(container, name, config, ticketStatus) {
+ super( container, name, config, ticketStatus );
- Editor.prototype._refreshInspector = function (refreshContent) {
- var self = this;
- var lifecycle = self.lifecycle;
- var inspector = self.inspector;
- var node = self.inspectorNode;
-
- var params = { lifecycle: lifecycle };
-
- 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;
+ this.pointHandleRadius = 5;
}
- 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);
- 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();
- });
-
- refreshedNode.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.val() == option.val()) != flip) {
- jQuery(selector).show();
- } else {
- jQuery(selector).hide();
- }
- }
- field.change(function (e) { toggle() });
- toggle();
- });
-
- 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;
- var inspector = self.inspector;
-
- inspector.on('change', ':input', function () {
- var node = jQuery(this);
- var value;
+ initializeEditor(node, name, config, focusStatus) {
+ var self = this;
+ self.initializeViewer(node, name, config, focusStatus);
+ self.container.closest('form[name=ModifyLifecycle]').submit(function (e) {
+ var config = self.lifecycle.exportAsConfiguration();
+ var form = jQuery(this);
+ var field = jQuery('<input type="hidden" name="Config">');
+ field.val(JSON.stringify(config));
+ form.append(field);
+ return true;
+ });
+ }
- if (node.is('.combo-list')) {
- value = node.val();
- node = node.closest('.combobox').find('.combo-text');
- }
- else if (node.is(':checkbox')) {
- value = node[0].checked;
- }
- else {
- value = node.val();
- }
+ clickedStatus(d, p_el) {
+ self = this;
- var field = node.attr('name');
+ var circle = d3.select(p_el).select('circle')._groups[0][0];
- var action = node.closest('li.action');
- if (action.length) {
- var action = lifecycle.itemForKey(action.data('key'));
- lifecycle.updateItem(action, field, value);
- }
- else if (inspector.find('.canvas').length) {
- lifecycle.update(field, value);
- }
- else {
- lifecycle.updateItem(self.inspectorNode, field, value);
- }
+ let current_val = d.name;
+ d.name = '';
self.renderDisplay();
- });
- inspector.on('click', 'button.change-color', function (e) {
- e.preventDefault();
- var inputContainer = jQuery(this).closest('.color-control');
- var field = inputContainer.data('field');
- var pickerContainer = jQuery('tr.color-widget[data-field="'+field+'"]');
- var picker = pickerContainer.find('.color-picker');
- jQuery(this).remove();
-
- var skipUpdateCallback = 0;
- var farb = jQuery.farbtastic(picker, function (newColor) {
- if (skipUpdateCallback) {
- return;
- }
- inputContainer.find('.current-color').val(newColor);
- lifecycle.updateItem(self.inspectorNode, field, newColor, true);
+ var frm = d3.select(p_el).append("foreignObject");
+
+ var circle_d3 = d3.select(circle);
+ var inp = frm
+ .attr("x", circle_d3.attr('cx') - circle_d3.attr('r') + 10)
+ .attr("y", circle_d3.attr('cy') - circle_d3.attr('r') / 2 + 4)
+ .attr("width", circle_d3.attr('r') * 2)
+ .attr("height", 20)
+ .append("xhtml:form")
+ .append("input")
+ .attr("value", function() {
+ this.focus();
+
+ return current_val;
+ })
+ .attr("style", "width: 294px; background: transparent; border: none;")
+ // make the form go away when you jump out (form looses focus) or hit ENTER:
+ .on("blur", function() {
+ d.name = inp.node().value;
+ RT.Lifecycle.updateStatusName( current_val, d.name );
+
+ // Note to self: frm.remove() will remove the entire <g> group! Remember the D3 selection logic!
+ d3.select("foreignObject").remove();
self.renderDisplay();
- });
- farb.setColor(self.inspectorNode[field]);
-
- // see farbtastic's implementation
- jQuery('*', picker).mousedown(function () {
- self.lifecycle.beginChangingColor();
- });
-
- var input = jQuery('<input class="current-color" size=8 maxlength=7>');
- inputContainer.find('.current-color').replaceWith(input);
- input.on('input', function () {
- var newColor = input.val();
- if (newColor.match(/^#[a-fA-F0-9]{6}$/)) {
- skipUpdateCallback = 1;
- farb.setColor(newColor);
- skipUpdateCallback = 0;
-
- lifecycle.updateItem(self.inspectorNode, field, newColor);
+ })
+ .on("keypress", function() {
+ // 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();
+
+ d.name = inp.node().value;
+
+ // odd. Should work in Safari, but the debugger crashes on this instead.
+ // Anyway, it SHOULD be here and it doesn't hurt otherwise.
+ d3.select("foreignObject").remove();
self.renderDisplay();
}
- });
- input.val(self.inspectorNode[field]);
- });
-
- inspector.on('click', 'button.delete', function (e) {
- e.preventDefault();
-
- var action = jQuery(this).closest('li.action');
- if (action.length) {
- lifecycle.deleteActionForTransition(self.inspectorNode, action.data('key'));
- action.slideUp(200, function () { jQuery(this).remove() });
- }
- else {
- lifecycle.deleteItemForKey(self.inspectorNode._key);
- self.defocus();
- }
- });
-
- inspector.on('click', 'button.clone', function (e) {
- e.preventDefault();
- var p = self.viewportCenterPoint();
- var clone = self.lifecycle.cloneItem(self.inspectorNode, p[0], p[1]);
- self.focusItem(clone);
- });
-
- inspector.on('click', 'button.add-action', function (e) {
- e.preventDefault();
- var action = lifecycle.createActionForTransition(self.inspectorNode);
-
- var params = {action:action, lifecycle:lifecycle};
- var html = self.templates.action(params);
- jQuery(html).appendTo(inspector.find('ul.actions'))
- .hide()
- .slideDown(200);
- });
-
- inspector.on('click', 'a.add-transition', function (e) {
- e.preventDefault();
- var button = jQuery(this);
- var fromStatus = button.data('from');
- var toStatus = button.data('to');
-
- lifecycle.addTransition(fromStatus, toStatus);
-
- button.closest('li').addClass('hidden');
-
- inspector.find('a.select-transition[data-from="'+fromStatus+'"][data-to="'+toStatus+'"]').closest('li').removeClass('hidden');
-
- self.renderDisplay();
- });
-
- inspector.on('click', 'a.select-status', function (e) {
- e.preventDefault();
- var statusName = jQuery(this).data('name');
- var d = self.lifecycle.statusObjectForName(statusName);
- self.focusItem(d);
- });
-
- inspector.on('mouseenter', 'a.select-status', function (e) {
- var statusName = jQuery(this).data('name');
- var d = self.lifecycle.statusObjectForName(statusName);
- self.hoverItem(d);
- });
-
- inspector.on('mouseenter', 'a.add-transition', function (e) {
- var statusName = jQuery(this).data('to');
- var d = self.lifecycle.statusObjectForName(statusName);
- self.hoverItem(d);
- });
-
- inspector.on('click', 'a.select-transition', function (e) {
- e.preventDefault();
- var button = jQuery(this);
- var fromStatus = button.data('from');
- var toStatus = button.data('to');
-
- var d = self.lifecycle.hasTransition(fromStatus, toStatus);
- self.focusItem(d);
- });
-
- inspector.on('mouseenter', 'a.select-transition', function (e) {
- var button = jQuery(this);
- var fromStatus = button.data('from');
- var toStatus = button.data('to');
-
- var d = self.lifecycle.hasTransition(fromStatus, toStatus);
- self.hoverItem(d);
- });
-
- inspector.on('click', 'a.select-decoration', function (e) {
- e.preventDefault();
- var key = jQuery(this).data('key');
- var d = self.lifecycle.itemForKey(key);
- self.focusItem(d);
- });
-
- inspector.on('mouseenter', 'a.select-decoration', function (e) {
- var key = jQuery(this).data('key');
- var d = self.lifecycle.itemForKey(key);
- self.hoverItem(d);
- });
-
- inspector.on('mouseleave', 'a.select-status, a.add-transition, a.select-transition, a.select-decoration', function () {
- self.hoverItem(null);
- });
-
- inspector.on('click', '.add-status', function (e) {
- e.preventDefault();
- self.addNewStatus();
- });
-
- inspector.on('click', '.add-text', function (e) {
- e.preventDefault();
- self.addNewTextDecoration();
- });
-
- inspector.on('click', '.add-polygon', function (e) {
- e.preventDefault();
- self.addNewPolygonDecoration(jQuery(this).data('type'));
- });
-
- inspector.on('click', '.add-circle', function (e) {
- e.preventDefault();
- self.addNewCircleDecoration();
- });
-
- inspector.on('click', '.add-line', function (e) {
- e.preventDefault();
- self.addNewLineDecoration();
- });
-
- inspector.on('click', 'button.undo', function (e) {
- e.preventDefault();
- var frame = self.lifecycle.undo();
- var uiState = frame[1];
-
- if (uiState.focusKey) {
- var node = self.lifecycle.itemForKey(uiState.focusKey);
- self.focusItem(node);
- }
- else {
- self.defocus();
- }
- });
-
- inspector.on('click', 'button.redo', function (e) {
- e.preventDefault();
- var frame = self.lifecycle.redo();
- var uiState = frame[1];
-
- if (uiState.focusKey) {
- var node = self.lifecycle.itemForKey(uiState.focusKey);
- self.focusItem(node);
- }
- else {
- self.defocus();
- }
- });
-
- inspector.on('focus', 'textarea[name=text]', function (e) {
- if (jQuery(this).val() == jQuery(this).data('default')) {
- jQuery(this).val("");
- }
- });
- };
-
- Editor.prototype.addPointHandles = function (d) {
- var self = this;
- var points = [];
-
- if (d._type == 'circle') {
- points.push({
- _key: d._key + '-r',
- x: this.xScaleZeroInvert(d.r + this.pointHandleRadius/2),
- y: 0
+ d.name = inp.node().value;
+ RT.Lifecycle.updateStatusName( current_val, d.name );
});
}
- else {
- for (var i = 0; i < d.points.length; ++i) {
- points.push({
- _key: d._key + '-' + i,
- i: i,
- x: d.points[i].x,
- y: d.points[i].y
- });
- }
+ clickedTransition(d) {
+ this.focusItem(d);
}
- self.pointHandles = points;
- };
-
- Editor.prototype.removePointHandles = function () {
- if (!this.pointHandles) {
- return;
+ clickedDecoration(d) {
+ this.focusItem(d);
}
-
- delete this.pointHandles;
- this.renderDecorations();
- };
-
- Editor.prototype.didDragPointHandle = function (d, node) {
- var x = this.xScaleZeroInvert(d3.event.x);
- var y = this.yScaleZeroInvert(d3.event.y);
-
- if (this.xScaleZero(x) == this.xScaleZero(d.x) && this.yScaleZero(y) == this.yScaleZero(d.y)) {
- return;
- }
-
- if (!d._dragging) {
- this.lifecycle.beginDragging();
- d._dragging = true;
- }
-
- d.x = x;
- d.y = y;
-
- if (this.inspectorNode._type == 'circle') {
- this.lifecycle.moveCircleRadiusPoint(this.inspectorNode, this.xScaleZero(x), this.yScaleZero(y));
+ viewportCenterPoint() {
+ var rect = this.svg.node().getBoundingClientRect();
+ var x = (rect.width / 2 - this._currentZoom.x) / this._currentZoom.k;
+ var y = (rect.height / 2 - this._currentZoom.y) / this._currentZoom.k;
+ return [this.xScaleInvert(x), this.yScaleInvert(y)];
}
- else {
- this.lifecycle.movePolygonPoint(this.inspectorNode, d.i, x, y);
+ addNewStatus() {
+ var p = this.viewportCenterPoint();
+ var status = this.lifecycle.createStatus(p[0], p[1]);
+ this.focusItem(status);
}
- this.renderDisplay();
- };
-
- // add rects under text decorations for highlighting
- Editor.prototype.renderTextDecorations = function (initial) {
- Super.prototype.renderTextDecorations.call(this, initial);
- var self = this;
- self.renderTextDecorationBackgrounds(initial);
- };
- Editor.prototype.renderTextDecorationBackgrounds = function (initial) {
- var self = this;
- var rects = self.decorationContainer.selectAll("rect.text-background")
- .data(self.lifecycle.decorations.text, function (d) { return d._key });
- rects.exit()
- .classed("removing", true)
- .transition().duration(200*self.animationFactor)
- .remove();
- var newRects = rects.enter().insert("rect", ":first-child")
- .attr("data-key", function (d) { return d._key })
- .classed("text-background", true)
- .on("click", function (d) {
- d3.event.stopPropagation();
- self.clickedDecoration(d);
- })
- .call(function (rects) { self.didEnterTextDecorations(rects) });
- if (!initial) {
- newRects.style("opacity", 0.15)
- .transition().duration(200*self.animationFactor)
- .style("opacity", 1)
- .on("end", function () { d3.select(this).style("opacity", undefined) });
- }
- newRects.merge(rects)
- .classed("focus", function (d) { return self.isFocused(d) })
- .each(function (d) {
- var rect = d3.select(this);
- var label = self.decorationContainer.select("text[data-key='"+d._key+"']");
- var bbox = label.node().getBoundingClientRect();
- var width = bbox.width / self._currentZoom.k;
- var height = bbox.height / self._currentZoom.k;
- var padding = 5 / self._currentZoom.k;
- rect.attr("x", self.xScale(d.x)-padding)
- .attr("y", self.yScale(d.y)-padding)
- .attr("width", width+padding*2)
- .attr("height", height+padding*2);
- });
- };
- Editor.prototype.renderPolygonDecorations = function (initial) {
- Super.prototype.renderPolygonDecorations.call(this, initial);
- var self = this;
- var handles = self.transformContainer.selectAll("circle.point-handle")
- .data(self.pointHandles || [], function (d) { return d._key });
- handles.exit()
- .remove();
- var newHandles = handles.enter().append("circle")
- .classed("point-handle", true)
- .attr("r", self.pointHandleRadius)
- .call(d3.drag()
- .subject(function (d) { return { x: self.xScaleZero(d.x), y : self.yScaleZero(d.y) } })
- .on("start", function (d) { self.didBeginDrag(d, this) })
- .on("drag", function (d) { self.didDragPointHandle(d) })
- .on("end", function (d) { self.didEndDrag(d, this) })
- );
- if (!initial) {
- newHandles.style("opacity", 0.15)
- .transition().duration(200*self.animationFactor)
- .style("opacity", 1)
- .on("end", function () { d3.select(this).style("opacity", undefined) });
- }
-
- newHandles.merge(handles)
- .attr("transform", function (d) {
- var x = self.xScale(self.inspectorNode.x);
- var y = self.yScale(self.inspectorNode.y);
- if (self.inspectorNode._type == 'line') {
- y += 20;
- }
- return "translate(" + x + ", " + y + ")";
- })
- .attr("cx", function (d) { return self.xScaleZero(d.x) })
- .attr("cy", function (d) { return self.yScaleZero(d.y) })
- };
-
- Editor.prototype.clickedStatus = function (d) {
- this.focusItem(d);
- };
-
- Editor.prototype.clickedTransition = function (d) {
- this.focusItem(d);
- };
-
- Editor.prototype.clickedDecoration = function (d) {
- this.focusItem(d);
- };
-
- Editor.prototype.didBeginDrag = function (d, node) { };
-
- Editor.prototype.didEndDrag = function (d, node) {
- d._dragging = false;
- };
-
- Editor.prototype.didDragItem = function (d, node) {
- if (this.inspectorNode && this.inspectorNode._key != d._key) {
- return;
+ _refreshLifecycleUI(refreshContent) {
+ var self = this;
+ var lifecycle = self.lifecycle;
+ var inspector = self.inspector;
+ var node = self.inspectorNode;
+ var params = { lifecycle: lifecycle };
+ var header = inspector.find('.header');
+ var refreshedNode = header;
+ if (refreshContent) {
+ var type = node ? node._type : 'canvas';
+ params[type] = node;
+ inspector.find('.content').html(self.templates[type](params));
+ refreshedNode = inspector;
+ }
+ 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);
+ 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();
+ });
+ refreshedNode.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.val() == option.val()) != flip) {
+ jQuery(selector).show();
+ }
+ else {
+ jQuery(selector).hide();
+ }
+ };
+ field.change(function (e) { toggle(); });
+ toggle();
+ });
+ refreshedNode.find(".combobox input.combo-text").each(function () {
+ ComboBox_Load(this.id);
+ });
}
- 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;
+ // add rects under text decorations for highlighting
+ // renderTextDecorations(initial) {
+ // Super.prototype.renderTextDecorations.call(this, initial);
+ // var self = this;
+ // self.renderTextDecorationBackgrounds(initial);
+ // }
+ renderTextDecorationBackgrounds(initial) {
+ var self = this;
+ var rects = self.decorationContainer.selectAll("rect.text-background")
+ .data(self.lifecycle.decorations.text, function (d) { return d._key; });
+ rects.exit()
+ .classed("removing", true)
+ .transition().duration(200 * self.animationFactor)
+ .remove();
+ var newRects = rects.enter().insert("rect", ":first-child")
+ .attr("data-key", function (d) { return d._key; })
+ .classed("text-background", true)
+ .on("click", function (d) {
+ d3.event.stopPropagation();
+ self.clickedDecoration(d);
+ })
+ .call(function (rects) { self.didEnterTextDecorations(rects); });
+ if (!initial) {
+ newRects.style("opacity", 0.15)
+ .transition().duration(200 * self.animationFactor)
+ .style("opacity", 1)
+ .on("end", function () { d3.select(this).style("opacity", undefined); });
+ }
+ newRects.merge(rects)
+ .classed("focus", function (d) { return self.isFocused(d); })
+ .each(function (d) {
+ var rect = d3.select(this);
+ var label = self.decorationContainer.select("text[data-key='" + d._key + "']");
+ var bbox = label.node().getBoundingClientRect();
+ var width = bbox.width / self._currentZoom.k;
+ var height = bbox.height / self._currentZoom.k;
+ var padding = 5 / self._currentZoom.k;
+ rect.attr("x", self.xScale(d.x) - padding)
+ .attr("y", self.yScale(d.y) - padding)
+ .attr("width", width + padding * 2)
+ .attr("height", height + padding * 2);
+ });
}
- if (!d._dragging) {
- this.lifecycle.beginDragging();
- d._dragging = true;
+ didBeginDrag(d, node) { }
+ didEndDrag(d, node) {
+ d._dragging = false;
}
-
- this.lifecycle.moveItem(d, x, y);
- this.renderDisplay();
- };
-
- Editor.prototype._createDrag = function () {
- var self = this;
- return d3.drag()
- .subject(function (d) { return { x: self.xScale(d.x), y : self.yScale(d.y) } })
- .on("start", function (d) { self.didBeginDrag(d, this) })
- .on("drag", function (d) { self.didDragItem(d, this) })
- .on("end", function (d) { self.didEndDrag(d, this) })
- };
-
- Editor.prototype.didEnterStatusNodes = function (statuses) {
- statuses.call(this._createDrag());
- };
-
- Editor.prototype.didEnterTextDecorations = function (labels) {
- labels.call(this._createDrag());
- };
-
- Editor.prototype.didEnterPolygonDecorations = function (polygons) {
- polygons.call(this._createDrag());
- };
-
- Editor.prototype.didEnterCircleDecorations = function (circles) {
- circles.call(this._createDrag());
- };
-
- Editor.prototype.didEnterLineDecorations = function (lines) {
- lines.call(this._createDrag());
- };
-
- Editor.prototype.viewportCenterPoint = function () {
- var rect = this.svg.node().getBoundingClientRect();
- var x = (rect.width / 2 - this._currentZoom.x)/this._currentZoom.k;
- var y = (rect.height / 2 - this._currentZoom.y)/this._currentZoom.k;
- return [this.xScaleInvert(x), this.yScaleInvert(y)];
- };
-
- Editor.prototype.addNewStatus = function () {
- var p = this.viewportCenterPoint();
- var status = this.lifecycle.createStatus(p[0], p[1]);
- this.focusItem(status);
- };
-
- Editor.prototype.addNewTextDecoration = function () {
- var p = this.viewportCenterPoint();
- var text = this.lifecycle.createTextDecoration(p[0], p[1]);
- this.focusItem(text);
- };
-
- Editor.prototype.addNewPolygonDecoration = function (type) {
- var p = this.viewportCenterPoint();
- var polygon = this.lifecycle.createPolygonDecoration(p[0], p[1], type);
- this.focusItem(polygon);
- };
-
- Editor.prototype.addNewCircleDecoration = function () {
- var p = this.viewportCenterPoint();
- var circle = this.lifecycle.createCircleDecoration(p[0], p[1], this.statusCircleRadius);
- this.focusItem(circle);
- };
-
- Editor.prototype.addNewLineDecoration = function () {
- var p = this.viewportCenterPoint();
- var line = this.lifecycle.createLineDecoration(p[0], p[1]);
- this.focusItem(line);
- };
-
- Editor.prototype.initializeEditor = function (node, name, config, focusStatus) {
- var self = this;
- self.initializeViewer(node, name, config, focusStatus);
-
- self.templates = self._initializeTemplates(self.container);
- self.inspector = self.container.find('.inspector');
-
- self.setInspectorContent(null);
- self.bindInspectorEvents();
-
- self.container.closest('form[name=ModifyLifecycle]').submit(function (e) {
- var config = self.lifecycle.exportAsConfiguration();
- var form = jQuery(this);
- var field = jQuery('<input type="hidden" name="Config">');
- field.val(JSON.stringify(config));
- form.append(field);
- return true;
- });
-
- self.svg.on('click', function () { self.defocus() });
-
- self.lifecycle.undoFrameCallback = function (frame) {
- var uiState = {};
- if (self._focusItem) {
- uiState.focusKey = self._focusItem._key;
+ didDragItem(d, node) {
+ if (this.inspectorNode && this.inspectorNode._key != d._key) {
+ return;
}
- frame.push(uiState);
- };
-
- self.lifecycle.undoStateChangedCallback = function () {
- self._refreshInspector(false);
- };
- self.lifecycle.undoStateChangedCallback();
-
- setTimeout(function () {
- jQuery('.results').slideUp();
- }, 10*1000);
- };
-
- Editor.prototype.defocus = function () {
- Super.prototype.defocus.call(this);
- this.setInspectorContent(null);
- this.removePointHandles();
- this.hoverItem(null);
- this.renderDisplay();
- };
-
- Editor.prototype.focusItem = function (item) {
- Super.prototype.focusItem.call(this, item);
- this.setInspectorContent(item);
-
- if (item._type == 'polygon' || item._type == 'line' || item._type == 'circle') {
- this.addPointHandles(item);
+ 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;
+ }
+ if (!d._dragging) {
+ this.lifecycle.beginDragging();
+ d._dragging = true;
+ }
+ this.lifecycle.moveItem(d, x, y);
+ this.renderDisplay();
}
-
- this.renderDisplay();
- };
-
- Editor.prototype.hoverItem = function (item) {
- this.svg.selectAll(".hover").classed('hover', false);
-
- if (item) {
- this.svg.selectAll("*[data-key='"+item._key+"']").classed('hover', true);
+ _createDrag() {
+ var self = this;
+ return d3.drag()
+ .subject(function (d) { return { x: self.xScale(d.x), y: self.yScale(d.y) }; })
+ .on("start", function (d) { self.didBeginDrag(d, this); })
+ .on("drag", function (d) { self.didDragItem(d, this); })
+ .on("end", function (d) { self.didEndDrag(d, this); });
+ }
+ didEnterStatusNodes(statuses) {
+ statuses.call(this._createDrag());
+ }
+ didEnterTextDecorations(labels) {
+ labels.call(this._createDrag());
+ }
+ didEnterLineDecorations(lines) {
+ lines.call(this._createDrag());
}
};
-
- RT.LifecycleEditor = Editor;
});
diff --git a/share/static/js/lifecycleui-viewer.js b/share/static/js/lifecycleui-viewer.js
index 85a190650..fe7bf7dc1 100644
--- a/share/static/js/lifecycleui-viewer.js
+++ b/share/static/js/lifecycleui-viewer.js
@@ -1,584 +1,511 @@
jQuery(function () {
- function Viewer (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
- };
-
- Viewer.prototype.createScale = function (size, padding) {
- return d3.scaleLinear()
- .domain([0, 10000])
- .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.xScaleZero = function (x) { return this.gridScale(this._xScaleZero(x)) };
- Viewer.prototype.yScaleZero = function (y) { return this.gridScale(this._yScaleZero(y)) };
- Viewer.prototype.xScaleInvert = function (x) { return Math.floor(this._xScale.invert(x)) };
- Viewer.prototype.yScaleInvert = function (y) { return Math.floor(this._yScale.invert(y)) };
- Viewer.prototype.xScaleZeroInvert = function (x) { return Math.floor(this._xScaleZero.invert(x)) };
- Viewer.prototype.yScaleZeroInvert = function (y) { return Math.floor(this._yScaleZero.invert(y)) };
-
- Viewer.prototype.addZoomBehavior = function () {
- var self = this;
- self._zoom = d3.zoom()
- .scaleExtent([.3, 2])
- .on("zoom", function () {
- if (self.zoomControl) {
- self.didZoom();
- }
- });
-
- self.svg.call(self._zoom);
- };
-
- Viewer.prototype.didZoom = function () {
- this._currentZoom = d3.event.transform;
- this.transformContainer.attr("transform", d3.event.transform);
- };
-
- Viewer.prototype.zoomScale = function (scaleBy, animated) {
- if (animated) {
- this.svg.transition()
- .duration(350*this.animationFactor)
- .call(this._zoom.scaleBy, scaleBy);
+ 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
}
- else {
- this.svg.call(this._zoom.scaleBy, scaleBy);
+ createScale(size, padding) {
+ return d3.scaleLinear()
+ .domain([0, 10000])
+ .range([padding, size - padding]);
}
- }
-
- Viewer.prototype._setZoom = function (zoom, animated) {
- if (animated) {
- this.svg.transition()
- .duration(750*this.animationFactor)
+ 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);
+ }
}
- else {
- this.svg.call(this._zoom.transform, zoom);
+ resetZoom(animated) {
+ this._setZoom(this._zoomIdentity, animated);
}
- };
-
- Viewer.prototype.resetZoom = function (animated) {
- this._setZoom(this._zoomIdentity, animated);
- };
-
- Viewer.prototype.zoomToFit = function (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);
- };
-
- Viewer.prototype.didEnterStatusNodes = function (statuses) { };
- Viewer.prototype.didEnterTransitions = function (paths) { };
- Viewer.prototype.didEnterTextDecorations = function (labels) { };
- Viewer.prototype.didEnterPolygonDecorations = function (polygons) { };
- Viewer.prototype.didEnterCircleDecorations = function (circles) { };
- Viewer.prototype.didEnterLineDecorations = function (lines) { };
-
- Viewer.prototype.renderStatusNodes = function (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 })
- .on("click", function (d) {
- d3.event.stopPropagation();
- self.clickedStatus(d);
- })
- .call(function (statuses) { self.didEnterStatusNodes(statuses) });
-
- newStatuses.append("circle")
- .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8)
-
- newStatuses.append("text");
-
- if (!initial) {
- newStatuses.transition().duration(200*self.animationFactor)
- .select("circle")
- .attr("r", self.statusCircleRadius)
+ 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);
}
-
- 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) })
- };
-
- Viewer.prototype.clickedStatus = function (d) { };
- Viewer.prototype.clickedTransition = function (d) { };
- Viewer.prototype.clickedDecoration = function (d) { };
-
- Viewer.prototype.truncateLabel = function (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();
+ 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; })
+ .on("click", function (d) {
+ d3.event.stopPropagation();
+ self.clickedStatus(d, this);
+ })
+ .call(function (statuses) { self.didEnterStatusNodes(statuses); });
+ newStatuses.append("circle")
+ .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8);
+ newStatuses.append("text");
+ 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); });
}
- };
-
- Viewer.prototype.transitionArc = function (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;
+ 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();
+ }
}
-
- 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;
- };
-
- Viewer.prototype.renderTransitions = function (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)
- })
- });
+ 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;
}
-
- };
-
- Viewer.prototype._wrapTextDecoration = function (node, text) {
- if (node.attr('data-text') == text) {
- return;
+ 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);
+ });
+ });
+ }
}
-
- var lines = text.split(/\n/),
- lineHeight = 1.1;
-
- if (node.attr('data-text')) {
- node.selectAll("*").remove();
+ _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]);
+ }
}
- 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); });
}
- };
-
- Viewer.prototype.renderTextDecorations = function (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) });
+ 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); });
}
-
- 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) })
- };
-
- Viewer.prototype.renderPolygonDecorations = function (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) });
+ 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); });
}
-
- 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) })
- };
-
- Viewer.prototype.renderCircleDecorations = function (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) });
+ 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 + ")"; });
}
-
- 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) })
- };
-
- Viewer.prototype.renderLineDecorations = function (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)
- })
- });
+ renderDecorations(initial) {
+ this.renderPolygonDecorations(initial);
+ this.renderCircleDecorations(initial);
+ this.renderLineDecorations(initial);
+ this.renderTextDecorations(initial);
}
-
- 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 + ")" })
- };
-
- Viewer.prototype.renderDecorations = function (initial) {
- this.renderPolygonDecorations(initial);
- this.renderCircleDecorations(initial);
- this.renderLineDecorations(initial);
- this.renderTextDecorations(initial);
- };
-
- Viewer.prototype.renderDisplay = function (initial) {
- this.renderTransitions(initial);
- this.renderStatusNodes(initial);
- this.renderDecorations(initial);
- };
-
- Viewer.prototype.centerOnItem = function (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);
- };
-
- Viewer.prototype.defocus = function () {
- this._focusItem = null;
- this.svg.classed("has-focus", false)
+ 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);
- };
-
- Viewer.prototype.focusItem = function (d) {
- this.defocus();
-
- this._focusItem = d;
- this.svg.classed("has-focus", true)
- .attr('data-focus-type', d._type);
- };
-
- Viewer.prototype.focusOnStatus = function (statusName, center, animated) {
- if (!statusName) {
- return;
}
-
- var meta = this.lifecycle.statusObjectForName(statusName);
- this.focusItem(meta);
-
- if (center) {
- this.centerOnItem(meta, animated)
+ focusItem(d) {
+ this.defocus();
+ this._focusItem = d;
+ this.svg.classed("has-focus", true)
+ .attr('data-focus-type', d._type);
}
- };
-
- Viewer.prototype.isFocused = function (d) {
- if (!this._focusItem) {
- return false;
+ focusOnStatus(statusName, center, animated) {
+ if (!statusName) {
+ return;
+ }
+ var meta = this.lifecycle.statusObjectForName(statusName);
+ this.focusItem(meta);
+ if (center) {
+ this.centerOnItem(meta, animated);
+ }
}
- return this._focusItem._key == d._key;
- };
-
- Viewer.prototype.isFocusedTransition = function (d, isFrom) {
- if (!this._focusItem) {
- return false;
+ isFocused(d) {
+ if (!this._focusItem) {
+ return false;
+ }
+ return this._focusItem._key == d._key;
}
-
- if (d._type == 'status') {
- if (this._focusItem._type == 'status') {
- if (isFrom) {
- return this.lifecycle.hasTransition(d.name, this._focusItem.name);
+ 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 {
- 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 (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;
}
- else if (d._type == 'transition') {
- if (this._focusItem._type == 'status') {
- if (isFrom) {
- return d.to == this._focusItem.name;
+
+ 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);
+ 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')) {
+ 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 {
- return d.from == this._focusItem.name;
+ 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);
+ });
}
+ }
+;
+
+
+
+
+
+
+
+
+
+
+
- return false;
- };
- Viewer.prototype.initializeViewer = function (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);
- 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')) {
- 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;
});
commit fae201ae174c0da50c1051088f6c2833beca9a05
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Oct 18 10:55:14 2019 -0400
On right click create new status node
diff --git a/share/html/Elements/Lifecycle/Graph b/share/html/Elements/Lifecycle/Graph
index a0d72d400..ca2421a33 100644
--- a/share/html/Elements/Lifecycle/Graph
+++ b/share/html/Elements/Lifecycle/Graph
@@ -62,9 +62,6 @@
</svg>
</div>
-% if ($Editing) {
- <& Inspector, %ARGS &>
-% }
% if ($Interactive) {
<& Interactive, %ARGS &>
% }
@@ -82,7 +79,7 @@
% }
% if ($Editing) {
- var editor = new RT.LifecycleEditor();
+ var editor = new RT.Editor( container, name, config, ticketStatus );
editor.initializeEditor(container, name, config, ticketStatus);
% } else {
% if ($Interactive) {
diff --git a/share/static/js/lifecycleui-editor.js b/share/static/js/lifecycleui-editor.js
index 297d80c33..887015395 100644
--- a/share/static/js/lifecycleui-editor.js
+++ b/share/static/js/lifecycleui-editor.js
@@ -16,8 +16,15 @@ jQuery(function () {
var field = jQuery('<input type="hidden" name="Config">');
field.val(JSON.stringify(config));
form.append(field);
+
return true;
});
+ self.container.on('contextmenu', function (e) {
+ e.preventDefault();
+
+ self.addNewStatus()
+ self.renderDisplay();
+ });
}
clickedStatus(d, p_el) {
@@ -47,8 +54,13 @@ jQuery(function () {
.attr("style", "width: 294px; background: transparent; border: none;")
// make the form go away when you jump out (form looses focus) or hit ENTER:
.on("blur", function() {
- d.name = inp.node().value;
- RT.Lifecycle.updateStatusName( current_val, d.name );
+ if ( inp.node().value && inp.node().value != current_val ) {
+ d.name = inp.node().value;
+ RT.Lifecycle.updateStatusName( current_val, d.name );
+ }
+ else {
+ d.name = current_val;
+ }
// Note to self: frm.remove() will remove the entire <g> group! Remember the D3 selection logic!
d3.select("foreignObject").remove();
@@ -68,15 +80,17 @@ jQuery(function () {
e.stopPropagation();
e.preventDefault();
- d.name = inp.node().value;
+ if ( inp.node().value && inp.node().value != current_val ) {
+ d.name = inp.node().value;
+ RT.Lifecycle.updateStatusName( current_val, d.name );
+ }
+ else {
+ d.name = current_val;
+ }
- // odd. Should work in Safari, but the debugger crashes on this instead.
- // Anyway, it SHOULD be here and it doesn't hurt otherwise.
d3.select("foreignObject").remove();
self.renderDisplay();
}
- d.name = inp.node().value;
- RT.Lifecycle.updateStatusName( current_val, d.name );
});
}
clickedTransition(d) {
@@ -93,8 +107,7 @@ jQuery(function () {
}
addNewStatus() {
var p = this.viewportCenterPoint();
- var status = this.lifecycle.createStatus(p[0], p[1]);
- this.focusItem(status);
+ this.lifecycle.createStatus(p[0], p[1]);
}
diff --git a/share/static/js/lifecycleui-model.js b/share/static/js/lifecycleui-model.js
index 5e75bc3cb..74c37c3a3 100644
--- a/share/static/js/lifecycleui-model.js
+++ b/share/static/js/lifecycleui-model.js
@@ -1,859 +1,712 @@
jQuery(function () {
var _ELEMENT_KEY_SEQ = 0;
- function Lifecycle (name) {
- this.name = name;
- this.type = 'ticket';
- this.is_ticket = true;
- this.statuses = [];
- this.defaults = {};
- this.transitions = [];
- this.decorations = {};
- this.ticket_zoom = 'dynamic';
- this.ticket_center = 'status';
-
- this.defaultColor = '#547CCC';
- this._undoState = { undoStack: [], redoStack: [] };
- this._keyMap = {};
- this._statusMeta = {};
- };
-
- Lifecycle.prototype._initialPointsForPolygon = {
- Line: [
- {x: -700, y: 0},
- {x: 700, y: 0},
- ],
- Triangle: [
- {x: 700, y: 2000},
- {x: 0, y: 0},
- {x: -600, y: 2000}
- ],
- Rectangle: [
- {x: -600, y: -600},
- {x: 600, y: -600},
- {x: 600, y: 600},
- {x: -600, y: 600}
- ]
- };
-
- Lifecycle.prototype.initializeFromConfig = function (config) {
- var self = this;
-
- if (config.type) {
- self.type = config.type;
- self.is_ticket = self.type == 'ticket';
- }
-
- if (config.ticket_display) {
- 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]);
- jQuery.each(config[type], function (j, statusName) {
- var item;
- if (config.statusExtra) {
- item = config.statusExtra[statusName] || {};
+ class Lifecycle {
+ constructor(name) {
+ this.name = name;
+ this.type = 'ticket';
+ this.is_ticket = true;
+ this.statuses = [];
+ this.defaults = {};
+ this.transitions = [];
+ this.decorations = {};
+ this.ticket_zoom = 'dynamic';
+ this.ticket_center = 'status';
+ this.defaultColor = '#547CCC';
+ this._undoState = { undoStack: [], redoStack: [] };
+ this._keyMap = {};
+ this._statusMeta = {};
+ }
+ initializeFromConfig(config) {
+ var self = this;
+ if (config.type) {
+ self.type = config.type;
+ self.is_ticket = self.type == 'ticket';
+ }
+ if (config.ticket_display) {
+ 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]);
+ jQuery.each(config[type], function (j, statusName) {
+ var item;
+ if (config.statusExtra) {
+ item = config.statusExtra[statusName] || {};
+ }
+ else {
+ item = {};
+ }
+ item._key = _ELEMENT_KEY_SEQ++;
+ item._type = 'status';
+ item.name = statusName;
+ item.type = type;
+ self._statusMeta[statusName] = item;
+ self._keyMap[item._key] = item;
+ });
+ }
+ });
+ var statusCount = self.statuses.length;
+ jQuery.each(self.statuses, function (i, statusName) {
+ var meta = self._statusMeta[statusName];
+ // arrange statuses evenly-spaced around a circle
+ if (!meta.x) {
+ meta.x = 10000 * (Math.sin(2 * Math.PI * (i / statusCount)) + 1) / 2;
+ meta.y = 10000 * (Math.cos(2 * Math.PI * (i / statusCount)) + 1) / 2;
+ }
+ ;
+ if (!meta.color) {
+ meta.color = self.defaultColor;
+ }
+ ;
+ });
+ if (config.defaults) {
+ self.defaults = config.defaults;
+ }
+ if (config.transitions) {
+ jQuery.each(config.transitions, function (fromStatus, toList) {
+ if (fromStatus == "") {
+ jQuery.each(toList, function (i, toStatus) {
+ self._statusMeta[toStatus].creation = true;
+ });
}
else {
- item = {};
+ jQuery.each(toList, function (i, toStatus) {
+ var description = fromStatus + ' -> ' + toStatus;
+ var transition;
+ if (config.transitionExtra) {
+ transition = config.transitionExtra[description] || {};
+ }
+ else {
+ transition = {};
+ }
+ 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;
+ });
}
- item._key = _ELEMENT_KEY_SEQ++;
- item._type = 'status';
- item.name = statusName;
- item.type = type;
- self._statusMeta[statusName] = item;
- self._keyMap[item._key] = item;
});
}
- });
-
- var statusCount = self.statuses.length;
-
- jQuery.each(self.statuses, function (i, statusName) {
- var meta = self._statusMeta[statusName];
- // arrange statuses evenly-spaced around a circle
- if (!meta.x) {
- meta.x = 10000 * (Math.sin(2 * Math.PI * (i/statusCount)) + 1) / 2;
- meta.y = 10000 * (Math.cos(2 * Math.PI * (i/statusCount)) + 1) / 2;
- };
-
- if (!meta.color) {
- meta.color = self.defaultColor;
- };
- });
-
- if (config.defaults) {
- self.defaults = config.defaults;
- }
-
- if (config.transitions) {
- jQuery.each(config.transitions, function (fromStatus, toList) {
- if (fromStatus == "") {
- jQuery.each(toList, function (i, toStatus) {
- self._statusMeta[toStatus].creation = true;
+ if (config.rights) {
+ jQuery.each(config.rights, function (description, right) {
+ jQuery.each(self.transitions, function (i, transition) {
+ var from = transition.from;
+ var to = transition.to;
+ if (description == (from + ' -> ' + to)
+ || description == ('* -> ' + to)
+ || description == (from + ' -> *')
+ || description == ('* -> *')) {
+ transition.right = right;
+ }
});
+ });
+ }
+ jQuery.each(self.transitions, function (i, transition) {
+ if (!transition.right) {
+ transition.right = self.defaultRightForTransition(transition);
}
- else {
- jQuery.each(toList, function (i, toStatus) {
- var description = fromStatus + ' -> ' + toStatus;
- var transition;
- if (config.transitionExtra) {
- transition = config.transitionExtra[description] || {};
- }
- else {
- transition = {};
+ });
+ if (config.actions) {
+ var actions = config.actions;
+ // convert hash-based actions to array of pairs
+ if (jQuery.type(config.actions) == "object") {
+ actions = [];
+ jQuery.each(config.actions, function (description, action) {
+ actions.push(description, action);
+ });
+ }
+ for (var i = 0; i < actions.length; ++i) {
+ var description;
+ var spec;
+ if (jQuery.type(actions[i]) == "string") {
+ description = actions[i];
+ spec = actions[++i];
+ }
+ else {
+ spec = actions[i];
+ var from = (delete spec.from) || '*';
+ var to = (delete spec.to) || '*';
+ description = from + ' -> ' + to;
+ }
+ jQuery.each(self.transitions, function (i, transition) {
+ var from = transition.from;
+ var to = transition.to;
+ if (description == (from + ' -> ' + to)
+ || description == ('* -> ' + to)
+ || description == (from + ' -> *')
+ || description == ('* -> *')) {
+ var action = jQuery.extend({}, spec);
+ action._key = _ELEMENT_KEY_SEQ++;
+ action._type = 'action';
+ transition.actions.push(action);
+ self._keyMap[action._key] = action;
}
- 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;
});
}
+ }
+ self.decorations = {};
+ jQuery.each(['text', 'polygon', 'circle', 'line'], function (i, type) {
+ var decorations = [];
+ if (config.decorations && 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[type] = decorations;
});
}
-
- if (config.rights) {
- jQuery.each(config.rights, function (description, right) {
- jQuery.each(self.transitions, function (i, transition) {
- var from = transition.from;
- var to = transition.to;
-
- if (description == (from + ' -> ' + to)
- || description == ('* -> ' + to)
- || description == (from + ' -> *')
- || description == ('* -> *')) {
- transition.right = right;
- }
- });
+ defaultRightForTransition(transition) {
+ if (this.type == 'asset') {
+ return 'ModifyAsset';
+ }
+ if (transition.to == 'deleted') {
+ return 'DeleteTicket';
+ }
+ return 'ModifyTicket';
+ }
+ _sanitizeForExport(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];
+ }
});
- }
-
- jQuery.each(self.transitions, function (i, transition) {
- if (!transition.right) {
- transition.right = self.defaultRightForTransition(transition);
+ // remove additional redundant information to provide a single source
+ // of truth
+ if (type == 'status') {
+ delete clone.name;
+ delete clone.type;
+ delete clone.creation;
}
- });
-
- if (config.actions) {
- var actions = config.actions;
-
- // convert hash-based actions to array of pairs
- if (jQuery.type(config.actions) == "object") {
- actions = [];
- jQuery.each(config.actions, function(description, action) {
- actions.push(description, action);
- });
+ else if (type == 'transition') {
+ delete clone.from;
+ delete clone.to;
+ delete clone.actions;
+ delete clone.right;
}
-
- for (var i = 0; i < actions.length; ++i) {
- var description;
- var spec;
-
- if (jQuery.type(actions[i]) == "string") {
- description = actions[i];
- spec = actions[++i];
+ return clone;
+ }
+ exportAsConfiguration() {
+ var self = this;
+ var config = {
+ type: self.type,
+ initial: [],
+ active: [],
+ inactive: [],
+ defaults: self.defaults,
+ actions: [],
+ rights: {},
+ transitions: self.transitions,
+ ticket_display: self.ticket_display,
+ ticket_zoom: self.ticket_zoom,
+ ticket_center: self.ticket_center,
+ decorations: {},
+ statusExtra: {},
+ transitionExtra: {}
+ };
+ var transitions = { "": [] };
+ jQuery.each(self.statuses, function (i, statusName) {
+ var meta = self._statusMeta[statusName];
+ var statusType = meta.type;
+ config[statusType].push(statusName);
+ config.statusExtra[statusName] = self._sanitizeForExport(meta);
+ if (meta.creation) {
+ transitions[""].push(statusName);
}
- else {
- spec = actions[i];
- var from = (delete spec.from) || '*';
- var to = (delete spec.to) || '*';
- description = from + ' -> ' + to;
+ });
+ jQuery.each(self.transitions, function (i, transition) {
+ var from = transition.from;
+ var to = transition.to;
+ var description = transition.from + ' -> ' + transition.to;
+ config.transitionExtra[description] = self._sanitizeForExport(transition);
+ if (!transitions[from]) {
+ transitions[from] = [];
}
-
- jQuery.each(self.transitions, function (i, transition) {
- var from = transition.from;
- var to = transition.to;
-
- if (description == (from + ' -> ' + to)
- || description == ('* -> ' + to)
- || description == (from + ' -> *')
- || description == ('* -> *')) {
- var action = jQuery.extend({}, spec);
- action._key = _ELEMENT_KEY_SEQ++;
- action._type = 'action';
- transition.actions.push(action);
- self._keyMap[action._key] = action;
+ transitions[from].push(to);
+ if (transition.right) {
+ config.rights[description] = transition.right;
+ }
+ jQuery.each(transition.actions, function (i, action) {
+ if (action.label) {
+ var serialized = { label: action.label };
+ if (action.update) {
+ serialized.update = action.update;
+ }
+ config.actions.push(description, serialized);
}
});
- }
- }
-
- self.decorations = {};
-
- jQuery.each(['text', 'polygon', 'circle', 'line'], function (i, type) {
- var decorations = [];
-
- if (config.decorations && 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;
+ });
+ 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));
});
- }
-
- self.decorations[type] = decorations;
- });
- };
-
- Lifecycle.prototype.defaultRightForTransition = function (transition) {
- if (this.type == 'asset') {
- return 'ModifyAsset';
+ config.decorations[type] = out;
+ });
+ return config;
+ }
+ updateStatusName(oldValue, newValue) {
+ var self = this;
+
+ // statusMeta key
+ var oldMeta = self._statusMeta[oldValue];
+ delete self._statusMeta[oldValue];
+ self._statusMeta[newValue] = oldMeta;
+ // statuses array value
+ var index = self.statuses.indexOf(oldValue);
+ self.statuses[index] = newValue;
+ // defaults
+ jQuery.each(self.defaults, function (key, statusName) {
+ if (statusName == oldValue) {
+ self.defaults[key] = newValue;
+ }
+ });
+ // transitions
+ jQuery.each(self.transitions, function (i, transition) {
+ if (transition.from == oldValue) {
+ transition.from = newValue;
+ }
+ if (transition.to == oldValue) {
+ transition.to = newValue;
+ }
+ });
}
-
- if (transition.to == 'deleted') {
- return 'DeleteTicket';
+ statusNameForKey(key) {
+ return this._keyMap[key].name;
}
-
- 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;
+ statusObjects() {
+ return Object.values(this._statusMeta);
}
-
- return clone;
- };
-
- Lifecycle.prototype.exportAsConfiguration = function () {
- var self = this;
- var config = {
- type: self.type,
- initial: [],
- active: [],
- inactive: [],
- defaults: self.defaults,
- actions: [],
- rights: {},
- transitions: self.transitions,
-
- ticket_display: self.ticket_display,
- ticket_zoom: self.ticket_zoom,
- ticket_center: self.ticket_center,
- decorations: {},
- statusExtra: {},
- transitionExtra: {}
- };
-
- var transitions = { "": [] };
-
- jQuery.each(self.statuses, function (i, statusName) {
- var meta = self._statusMeta[statusName];
- var statusType = meta.type;
- config[statusType].push(statusName);
- config.statusExtra[statusName] = self._sanitizeForExport(meta);
-
- if (meta.creation) {
- transitions[""].push(statusName);
- }
- });
-
- jQuery.each(self.transitions, function (i, transition) {
- var from = transition.from;
- var to = transition.to;
- var description = transition.from + ' -> ' + transition.to;
-
- config.transitionExtra[description] = self._sanitizeForExport(transition);
-
- if (!transitions[from]) {
- transitions[from] = [];
- }
- transitions[from].push(to);
-
- if (transition.right) {
- config.rights[description] = transition.right;
+ keyForStatusName(statusName) {
+ return this._statusMeta[statusName]._key;
+ }
+ statusObjectForName(statusName) {
+ return this._statusMeta[statusName];
+ }
+ deleteStatus(key) {
+ var self = this;
+ self._saveUndoEntry(false);
+ var statusName = self.statusNameForKey(key);
+ if (!statusName) {
+ console.error("no status for key '" + key + "'; did you accidentally pass status name?");
}
-
- jQuery.each(transition.actions, function (i, action) {
- if (action.label) {
- var serialized = { label : action.label };
- if (action.update) {
- serialized.update = action.update;
- }
- config.actions.push(description, serialized);
+ // internal book-keeping
+ delete self._statusMeta[statusName];
+ delete self._keyMap[key];
+ // statuses array value
+ var index = self.statuses.indexOf(statusName);
+ self.statuses.splice(index, 1);
+ // defaults
+ jQuery.each(self.defaults, function (key, value) {
+ if (value == statusName) {
+ delete self.defaults[key];
}
});
- });
-
- 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));
+ // transitions
+ self.transitions = jQuery.grep(self.transitions, function (transition) {
+ if (transition.from == statusName || transition.to == statusName) {
+ return false;
+ }
+ return true;
});
- config.decorations[type] = out;
- });
-
- return config;
- };
-
- Lifecycle.prototype.updateStatusName = function (oldValue, newValue) {
- var self = this;
-
- // statusMeta key
- var oldMeta = self._statusMeta[oldValue];
- delete self._statusMeta[oldValue];
- self._statusMeta[newValue] = oldMeta;
-
- // statuses array value
- var index = self.statuses.indexOf(oldValue);
- self.statuses[index] = newValue;
-
- // defaults
- jQuery.each(self.defaults, function (key, statusName) {
- if (statusName == oldValue) {
- self.defaults[key] = newValue;
- }
- });
-
- // transitions
- jQuery.each(self.transitions, function (i, transition) {
- if (transition.from == oldValue) {
- transition.from = newValue;
- }
- if (transition.to == oldValue) {
- transition.to = newValue;
- }
- });
- };
-
- Lifecycle.prototype.statusNameForKey = function (key) {
- return this._keyMap[key].name;
- };
-
- Lifecycle.prototype.statusObjects = function () {
- return Object.values(this._statusMeta);
- };
-
- Lifecycle.prototype.keyForStatusName = function (statusName) {
- return this._statusMeta[statusName]._key;
- };
-
- Lifecycle.prototype.statusObjectForName = function (statusName) {
- return this._statusMeta[statusName];
- };
-
- Lifecycle.prototype.deleteStatus = function (key) {
- var self = this;
-
- self._saveUndoEntry(false);
-
- var statusName = self.statusNameForKey(key);
- if (!statusName) {
- console.error("no status for key '" + key + "'; did you accidentally pass status name?");
+ self._undoStateChanged();
}
-
- // internal book-keeping
- delete self._statusMeta[statusName];
- delete self._keyMap[key];
-
- // statuses array value
- var index = self.statuses.indexOf(statusName);
- self.statuses.splice(index, 1);
-
- // defaults
- jQuery.each(self.defaults, function (key, value) {
- if (value == statusName) {
- delete self.defaults[key];
- }
- });
-
- // transitions
- self.transitions = jQuery.grep(self.transitions, function (transition) {
- if (transition.from == statusName || transition.to == statusName) {
+ addTransition(fromStatus, toStatus) {
+ this._saveUndoEntry(false);
+ var transition = {
+ _key: _ELEMENT_KEY_SEQ++,
+ _type: 'transition',
+ from: fromStatus,
+ to: toStatus,
+ style: 'solid',
+ actions: []
+ };
+ this.transitions.push(transition);
+ this._keyMap[transition._key] = transition;
+ transition.right = this.defaultRightForTransition(transition);
+ this._undoStateChanged();
+ return transition;
+ }
+ hasTransition(fromStatus, toStatus) {
+ if (fromStatus == toStatus || !fromStatus || !toStatus) {
return false;
}
- return true;
- });
-
- self._undoStateChanged();
- };
-
- Lifecycle.prototype.addTransition = function (fromStatus, toStatus) {
- this._saveUndoEntry(false);
-
- var transition = {
- _key : _ELEMENT_KEY_SEQ++,
- _type : 'transition',
- from : fromStatus,
- to : toStatus,
- style : 'solid',
- actions : []
- };
- this.transitions.push(transition);
- this._keyMap[transition._key] = transition;
-
- transition.right = this.defaultRightForTransition(transition);
-
- this._undoStateChanged();
-
- return transition;
- };
-
- Lifecycle.prototype.hasTransition = function (fromStatus, toStatus) {
- if (fromStatus == toStatus || !fromStatus || !toStatus) {
+ for (var i = 0; i < this.transitions.length; ++i) {
+ var transition = this.transitions[i];
+ if (transition.from == fromStatus && transition.to == toStatus) {
+ return transition;
+ }
+ }
+ ;
return false;
}
-
- for (var i = 0; i < this.transitions.length; ++i) {
- var transition = this.transitions[i];
- if (transition.from == fromStatus && transition.to == toStatus) {
- return transition;
- }
- };
-
- return false;
- };
-
- Lifecycle.prototype.transitionsFrom = function (fromStatus) {
- var transitions = [];
- for (var i = 0; i < this.transitions.length; ++i) {
- var transition = this.transitions[i];
- if (transition.from == fromStatus) {
- transitions.push(transition);
- }
- };
- return transitions;
- };
-
- Lifecycle.prototype.transitionsTo = function (toStatus) {
- var transitions = [];
- for (var i = 0; i < this.transitions.length; ++i) {
- var transition = this.transitions[i];
- if (transition.to == toStatus) {
- transitions.push(transition);
- }
- };
- return transitions;
- };
-
- Lifecycle.prototype.deleteTransition = function (key) {
- this._saveUndoEntry(false);
-
- this.transitions = jQuery.grep(this.transitions, function (transition) {
- if (transition._key == key) {
- return false;
+ transitionsFrom(fromStatus) {
+ var transitions = [];
+ for (var i = 0; i < this.transitions.length; ++i) {
+ var transition = this.transitions[i];
+ if (transition.from == fromStatus) {
+ transitions.push(transition);
+ }
}
- return true;
- });
- delete this._keyMap[key];
-
- this._undoStateChanged();
- };
-
- Lifecycle.prototype.deleteDecoration = function (type, key) {
- this._saveUndoEntry(false);
-
- this.decorations[type] = jQuery.grep(this.decorations[type], function (decoration) {
- if (decoration._key == key) {
- return false;
+ ;
+ return transitions;
+ }
+ transitionsTo(toStatus) {
+ var transitions = [];
+ for (var i = 0; i < this.transitions.length; ++i) {
+ var transition = this.transitions[i];
+ if (transition.to == toStatus) {
+ transitions.push(transition);
+ }
}
- return true;
- });
- delete this._keyMap[key];
-
- this._undoStateChanged();
- };
-
- Lifecycle.prototype.itemForKey = function (key) {
- return this._keyMap[key];
- };
-
- Lifecycle.prototype.deleteItemForKey = function (key) {
- var item = this.itemForKey(key);
- var type = item._type;
-
- if (type == 'status') {
- this.deleteStatus(key);
+ ;
+ return transitions;
}
- else if (type == 'transition') {
- this.deleteTransition(key);
+ deleteTransition(key) {
+ this._saveUndoEntry(false);
+ this.transitions = jQuery.grep(this.transitions, function (transition) {
+ if (transition._key == key) {
+ return false;
+ }
+ return true;
+ });
+ delete this._keyMap[key];
+ this._undoStateChanged();
}
- else if (type == 'text' || type == 'polygon' || type == 'circle' || type == 'line') {
- this.deleteDecoration(type, key);
+ deleteDecoration(type, key) {
+ this._saveUndoEntry(false);
+ this.decorations[type] = jQuery.grep(this.decorations[type], function (decoration) {
+ if (decoration._key == key) {
+ return false;
+ }
+ return true;
+ });
+ delete this._keyMap[key];
+ this._undoStateChanged();
}
- else {
- console.error("unhandled type '" + type + "'");
+ itemForKey(key) {
+ return this._keyMap[key];
}
- };
-
- Lifecycle.prototype.deleteActionForTransition = function (transition, key) {
- this._saveUndoEntry(false);
-
- transition.actions = jQuery.grep(transition.actions, function (action) {
- if (action._key == key) {
- return false;
+ deleteItemForKey(key) {
+ var item = this.itemForKey(key);
+ var type = item._type;
+ if (type == 'status') {
+ this.deleteStatus(key);
}
- return true;
- });
- delete this._keyMap[key];
-
- this._undoStateChanged();
- };
-
- Lifecycle.prototype.updateItem = function (item, field, newValue, skipUndo) {
- if (!skipUndo) {
+ else if (type == 'transition') {
+ this.deleteTransition(key);
+ }
+ else if (type == 'text' || type == 'polygon' || type == 'circle' || type == 'line') {
+ this.deleteDecoration(type, key);
+ }
+ else {
+ console.error("unhandled type '" + type + "'");
+ }
+ }
+ deleteActionForTransition(transition, key) {
this._saveUndoEntry(false);
+ transition.actions = jQuery.grep(transition.actions, function (action) {
+ if (action._key == key) {
+ return false;
+ }
+ return true;
+ });
+ delete this._keyMap[key];
+ this._undoStateChanged();
}
-
- var oldValue = item[field];
-
- item[field] = newValue;
-
- if (item._type == 'status' && field == 'name') {
- this.updateStatusName(oldValue, newValue);
+ updateItem(item, field, newValue, skipUndo) {
+ if (!skipUndo) {
+ this._saveUndoEntry(false);
+ }
+ var oldValue = item[field];
+ item[field] = newValue;
+ if (item._type == 'status' && field == 'name') {
+ this.updateStatusName(oldValue, newValue);
+ }
+ if (!skipUndo) {
+ this._undoStateChanged();
+ }
}
-
- if (!skipUndo) {
+ createActionForTransition(transition) {
+ this._saveUndoEntry(false);
+ var action = {
+ _type: 'action',
+ _key: _ELEMENT_KEY_SEQ++,
+ };
+ transition.actions.push(action);
+ this._keyMap[action._key] = action;
this._undoStateChanged();
+ return action;
}
- };
-
- Lifecycle.prototype.createActionForTransition = function (transition) {
- this._saveUndoEntry(false);
-
- var action = {
- _type : 'action',
- _key : _ELEMENT_KEY_SEQ++,
- };
- transition.actions.push(action);
- this._keyMap[action._key] = action;
-
- this._undoStateChanged();
-
- return action;
- };
-
- Lifecycle.prototype.beginDragging = function () {
- this._saveUndoEntry(true);
- };
-
- Lifecycle.prototype.beginChangingColor = function () {
- this._saveUndoEntry(true);
- };
-
- Lifecycle.prototype.moveItem = function (item, x, y) {
- item.x = x;
- item.y = y;
- };
-
- Lifecycle.prototype.moveCircleRadiusPoint = function (circle, x, y) {
- circle.r = Math.max(10, Math.sqrt(x**2 + y**2));
- }
-
- Lifecycle.prototype.movePolygonPoint = function (polygon, index, x, y) {
- var point = polygon.points[index];
- point.x = x;
- point.y = y;
- };
-
- Lifecycle.prototype.createStatus = function (x, y) {
- this._saveUndoEntry(false);
-
- var name;
- var i = 0;
- while (1) {
- name = 'status #' + ++i;
- if (!this._statusMeta[name]) {
- break;
- }
+ beginDragging() {
+ this._saveUndoEntry(true);
}
-
- this.statuses.push(name);
-
- var item = {
- _key: _ELEMENT_KEY_SEQ++,
- _type: 'status',
- name: name,
- type: 'initial',
- x: x,
- y: y
- };
- item.color = this.defaultColor;
-
- this._statusMeta[name] = item;
- this._keyMap[item._key] = item;
-
- this._undoStateChanged();
-
- return item;
- };
-
- Lifecycle.prototype.createTextDecoration = function (x, y) {
- this._saveUndoEntry(false);
-
- var item = {
- _key: _ELEMENT_KEY_SEQ++,
- _type: 'text',
- text: 'New label',
- x: x,
- y: y
- };
- this.decorations.text.push(item);
- this._keyMap[item._key] = item;
-
- this._undoStateChanged();
- return item;
- };
-
- Lifecycle.prototype.createPolygonDecoration = function (x, y, type) {
- this._saveUndoEntry(false);
-
- var item = {
- _key: _ELEMENT_KEY_SEQ++,
- _type: 'polygon',
- label: type,
- stroke: '#000000',
- renderStroke: true,
- strokeStyle: 'solid',
- fill: '#ffffff',
- renderFill: true,
- x: x,
- y: y,
- points: JSON.parse(JSON.stringify(this._initialPointsForPolygon[type]))
- };
- this.decorations.polygon.push(item);
- this._keyMap[item._key] = item;
-
- this._undoStateChanged();
- return item;
- };
-
- Lifecycle.prototype.createCircleDecoration = function (x, y, r) {
- this._saveUndoEntry(false);
-
- var item = {
- _key: _ELEMENT_KEY_SEQ++,
- _type: 'circle',
- label: 'Circle',
- stroke: '#000000',
- renderStroke: true,
- strokeStyle: 'solid',
- fill: '#ffffff',
- renderFill: true,
- x: x,
- y: y,
- r: r
- };
- this.decorations.circle.push(item);
- this._keyMap[item._key] = item;
-
- this._undoStateChanged();
- return item;
- };
-
- Lifecycle.prototype.createLineDecoration = function (x, y) {
- this._saveUndoEntry(false);
-
- var item = {
- _key: _ELEMENT_KEY_SEQ++,
- _type: 'line',
- label: 'Line',
- style: 'solid',
- startMarker: 'none',
- endMarker: 'arrowhead',
- x: x,
- y: y,
- points: JSON.parse(JSON.stringify(this._initialPointsForPolygon.Line))
- };
- this.decorations.line.push(item);
- this._keyMap[item._key] = item;
-
- this._undoStateChanged();
- return item;
- };
-
- Lifecycle.prototype.update = function (field, value) {
- this._saveUndoEntry(false);
-
- if (field == 'on_create' || field == 'approved' || field == 'denied' || field == 'reminder_on_open' || field == 'reminder_on_resolve') {
- this.defaults[field] = value;
+ beginChangingColor() {
+ this._saveUndoEntry(true);
}
- else if (field == 'ticket_display' || field == 'ticket_zoom' || field == 'ticket_center') {
- this[field] = value;
+ moveItem(item, x, y) {
+ item.x = x;
+ item.y = y;
}
- else {
- console.error("Unhandled field in Lifecycle.update: " + field);
+ moveCircleRadiusPoint(circle, x, y) {
+ circle.r = Math.max(10, Math.sqrt(x ** 2 + y ** 2));
}
-
- this._undoStateChanged();
- };
-
- Lifecycle.prototype._currentUndoFrame = function () {
- var undoState = this._undoState;
- var keyMap = this._keyMap;
- delete this._undoState;
- delete this._keyMap;
-
- var entry = JSON.stringify(this);
-
- this._undoState = undoState;
- this._keyMap = keyMap;
-
- var frame = [entry];
- if (this.undoFrameCallback) {
- this.undoFrameCallback(frame);
+ movePolygonPoint(polygon, index, x, y) {
+ var point = polygon.points[index];
+ point.x = x;
+ point.y = y;
}
-
- return frame;
- };
-
- Lifecycle.prototype._undoStateChanged = function () {
- this._canUndo = this._undoState.undoStack.length > 0;
- this._canRedo = this._undoState.redoStack.length > 0;
-
- if (this.undoStateChangedCallback) {
- this.undoStateChangedCallback();
+ createStatus(x, y) {
+ this._saveUndoEntry(false);
+ 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: x,
+ y: y
+ };
+ item.color = this.defaultColor;
+ this._statusMeta[name] = item;
+ this._keyMap[item._key] = item;
+ this._undoStateChanged();
+ return item;
}
- };
-
- Lifecycle.prototype._saveUndoEntry = function (notify) {
- var frame = this._currentUndoFrame();
- this._undoState.undoStack.push(frame);
- this._undoState.redoStack = [];
-
- if (notify) {
+ createTextDecoration(x, y) {
+ this._saveUndoEntry(false);
+ var item = {
+ _key: _ELEMENT_KEY_SEQ++,
+ _type: 'text',
+ text: 'New label',
+ x: x,
+ y: y
+ };
+ this.decorations.text.push(item);
+ this._keyMap[item._key] = item;
this._undoStateChanged();
+ return item;
}
- };
-
- Lifecycle.prototype._rebuildKeyMap = function () {
- var keyMap = {};
- jQuery.each(this._statusMeta, function (name, meta) {
- keyMap[meta._key] = meta;
- });
-
- jQuery.each(this.transitions, function (i, transition) {
- keyMap[transition._key] = transition;
- jQuery.each(transition.actions, function (j, action) {
- keyMap[action._key] = action;
+ createPolygonDecoration(x, y, type) {
+ this._saveUndoEntry(false);
+ var item = {
+ _key: _ELEMENT_KEY_SEQ++,
+ _type: 'polygon',
+ label: type,
+ stroke: '#000000',
+ renderStroke: true,
+ strokeStyle: 'solid',
+ fill: '#ffffff',
+ renderFill: true,
+ x: x,
+ y: y,
+ points: JSON.parse(JSON.stringify(this._initialPointsForPolygon[type]))
+ };
+ this.decorations.polygon.push(item);
+ this._keyMap[item._key] = item;
+ this._undoStateChanged();
+ return item;
+ }
+ createCircleDecoration(x, y, r) {
+ this._saveUndoEntry(false);
+ var item = {
+ _key: _ELEMENT_KEY_SEQ++,
+ _type: 'circle',
+ label: 'Circle',
+ stroke: '#000000',
+ renderStroke: true,
+ strokeStyle: 'solid',
+ fill: '#ffffff',
+ renderFill: true,
+ x: x,
+ y: y,
+ r: r
+ };
+ this.decorations.circle.push(item);
+ this._keyMap[item._key] = item;
+ this._undoStateChanged();
+ return item;
+ }
+ createLineDecoration(x, y) {
+ this._saveUndoEntry(false);
+ var item = {
+ _key: _ELEMENT_KEY_SEQ++,
+ _type: 'line',
+ label: 'Line',
+ style: 'solid',
+ startMarker: 'none',
+ endMarker: 'arrowhead',
+ x: x,
+ y: y,
+ points: JSON.parse(JSON.stringify(this._initialPointsForPolygon.Line))
+ };
+ this.decorations.line.push(item);
+ this._keyMap[item._key] = item;
+ this._undoStateChanged();
+ return item;
+ }
+ update(field, value) {
+ this._saveUndoEntry(false);
+ 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' || field == 'ticket_zoom' || field == 'ticket_center') {
+ this[field] = value;
+ }
+ else {
+ console.error("Unhandled field in Lifecycle.update: " + field);
+ }
+ this._undoStateChanged();
+ }
+ _currentUndoFrame() {
+ var undoState = this._undoState;
+ var keyMap = this._keyMap;
+ delete this._undoState;
+ delete this._keyMap;
+ var entry = JSON.stringify(this);
+ this._undoState = undoState;
+ this._keyMap = keyMap;
+ var frame = [entry];
+ if (this.undoFrameCallback) {
+ this.undoFrameCallback(frame);
+ }
+ return frame;
+ }
+ _undoStateChanged() {
+ this._canUndo = this._undoState.undoStack.length > 0;
+ this._canRedo = this._undoState.redoStack.length > 0;
+ if (this.undoStateChangedCallback) {
+ this.undoStateChangedCallback();
+ }
+ }
+ _saveUndoEntry(notify) {
+ var frame = this._currentUndoFrame();
+ this._undoState.undoStack.push(frame);
+ this._undoState.redoStack = [];
+ if (notify) {
+ this._undoStateChanged();
+ }
+ }
+ _rebuildKeyMap() {
+ var keyMap = {};
+ jQuery.each(this._statusMeta, function (name, meta) {
+ keyMap[meta._key] = meta;
});
- });
-
- jQuery.each(this.decorations, function (type, decorations) {
- jQuery.each(decorations, function (i, decoration) {
- keyMap[decoration._key] = decoration;
+ jQuery.each(this.transitions, function (i, transition) {
+ keyMap[transition._key] = transition;
+ jQuery.each(transition.actions, function (j, action) {
+ keyMap[action._key] = action;
+ });
});
- });
-
- this._keyMap = keyMap;
- };
-
- Lifecycle.prototype._restoreState = function (state) {
- for (var key in state) {
- this[key] = state[key];
- }
-
- this._rebuildKeyMap();
- };
-
- Lifecycle.prototype.undo = function () {
- var undoStack = this._undoState.undoStack;
- if (undoStack.length == 0) {
- return null;
+ jQuery.each(this.decorations, function (type, decorations) {
+ jQuery.each(decorations, function (i, decoration) {
+ keyMap[decoration._key] = decoration;
+ });
+ });
+ this._keyMap = keyMap;
}
-
- this._undoState.redoStack.push(this._currentUndoFrame());
-
- var frame = undoStack.pop();
- var entry = JSON.parse(frame[0]);
-
- this._restoreState(entry);
-
- this._undoStateChanged();
-
- return frame;
- };
-
- Lifecycle.prototype.redo = function () {
- var redoStack = this._undoState.redoStack;
- if (redoStack.length == 0) {
- return null;
+ _restoreState(state) {
+ for (var key in state) {
+ this[key] = state[key];
+ }
+ this._rebuildKeyMap();
}
-
- this._undoState.undoStack.push(this._currentUndoFrame());
-
- var frame = redoStack.pop();
- var entry = JSON.parse(frame[0]);
-
- this._restoreState(entry);
-
- this._undoStateChanged();
-
- return frame;
- };
-
- Lifecycle.prototype.cloneItem = function (source, x, y) {
- this._saveUndoEntry(false);
-
- var clone = JSON.parse(JSON.stringify(source));
- clone._key = _ELEMENT_KEY_SEQ++;
- clone.x = x;
- clone.y = y;
-
- if (clone._type == 'polygon' || clone._type == 'circle' || clone._type == 'line' || clone._type == 'text') {
- this.decorations[clone._type].push(clone);
+ undo() {
+ var undoStack = this._undoState.undoStack;
+ if (undoStack.length == 0) {
+ return null;
+ }
+ this._undoState.redoStack.push(this._currentUndoFrame());
+ var frame = undoStack.pop();
+ var entry = JSON.parse(frame[0]);
+ this._restoreState(entry);
+ this._undoStateChanged();
+ return frame;
}
- else {
- console.error("Unhandled type for clone: " + clone._type);
+ redo() {
+ var redoStack = this._undoState.redoStack;
+ if (redoStack.length == 0) {
+ return null;
+ }
+ this._undoState.undoStack.push(this._currentUndoFrame());
+ var frame = redoStack.pop();
+ var entry = JSON.parse(frame[0]);
+ this._restoreState(entry);
+ this._undoStateChanged();
+ return frame;
}
-
- this._keyMap[clone._key] = clone;
-
- this._undoStateChanged();
-
- return clone;
- };
-
- Lifecycle.prototype.selectedRights = function () {
- var rights = jQuery.map(this.transitions, function (transition) { return transition.right });
-
- if (this.type == 'ticket') {
- rights = rights.concat(['ModifyTicket', 'DeleteTicket']);
+ cloneItem(source, x, y) {
+ this._saveUndoEntry(false);
+ var clone = JSON.parse(JSON.stringify(source));
+ clone._key = _ELEMENT_KEY_SEQ++;
+ clone.x = x;
+ clone.y = y;
+ if (clone._type == 'polygon' || clone._type == 'circle' || clone._type == 'line' || clone._type == 'text') {
+ this.decorations[clone._type].push(clone);
+ }
+ else {
+ console.error("Unhandled type for clone: " + clone._type);
+ }
+ this._keyMap[clone._key] = clone;
+ this._undoStateChanged();
+ return clone;
}
- else if (this.type == 'asset') {
- rights = rights.concat(['ModifyAsset']);
+ selectedRights() {
+ var rights = jQuery.map(this.transitions, function (transition) { return transition.right; });
+ if (this.type == 'ticket') {
+ rights = rights.concat(['ModifyTicket', 'DeleteTicket']);
+ }
+ else if (this.type == 'asset') {
+ rights = rights.concat(['ModifyAsset']);
+ }
+ return jQuery.unique(rights.sort());
}
+ }
+;
- return jQuery.unique(rights.sort());
+ Lifecycle.prototype._initialPointsForPolygon = {
+ Line: [
+ {x: -700, y: 0},
+ {x: 700, y: 0},
+ ],
+ Triangle: [
+ {x: 700, y: 2000},
+ {x: 0, y: 0},
+ {x: -600, y: 2000}
+ ],
+ Rectangle: [
+ {x: -600, y: -600},
+ {x: 600, y: -600},
+ {x: 600, y: 600},
+ {x: -600, y: 600}
+ ]
};
- RT.Lifecycle = Lifecycle;
+ RT.Lifecycle = new Lifecycle();
});
diff --git a/share/static/js/lifecycleui-viewer.js b/share/static/js/lifecycleui-viewer.js
index fe7bf7dc1..93b725238 100644
--- a/share/static/js/lifecycleui-viewer.js
+++ b/share/static/js/lifecycleui-viewer.js
@@ -442,7 +442,8 @@ jQuery(function () {
scale = scale ** .6;
self._zoomIdentityScale = scale;
self._zoomIdentity = self._currentZoom = d3.zoomIdentity.scale(self._zoomIdentityScale);
- self.lifecycle = new RT.Lifecycle(name);
+ 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;
@@ -471,41 +472,8 @@ jQuery(function () {
e.preventDefault();
self.zoomScale(.75, true);
});
- self.container.on('click', 'button.zoom-reset', function (e) {
- e.preventDefault();
- self.resetZoom(true);
- });
}
- }
-;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ };
RT.LifecycleViewer = Viewer;
});
commit 72bb2b1a5573bf25ab54448b34716cc0329c8aae
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Oct 18 16:40:11 2019 -0400
Move lifecycle editor and viewer into one file
diff --git a/share/html/Elements/Lifecycle/Graph b/share/html/Elements/Lifecycle/Graph
index ca2421a33..99f0e4814 100644
--- a/share/html/Elements/Lifecycle/Graph
+++ b/share/html/Elements/Lifecycle/Graph
@@ -78,17 +78,10 @@
var ticketStatus = undefined;
% }
-% if ($Editing) {
var editor = new RT.Editor( container, name, config, ticketStatus );
+
editor.initializeEditor(container, name, config, ticketStatus);
-% } else {
-% if ($Interactive) {
- var viewer = new RT.LifecycleViewerInteractive();
-% } else {
- var viewer = new RT.LifecycleViewer();
-% }
- viewer.initializeViewer(container, name, config, ticketStatus);
-% }
+ editor.initializeViewer(container, name, config, ticketStatus);
});
});
</script>
diff --git a/share/html/Prefs/AboutMe.html b/share/html/Prefs/AboutMe.html
index e040cdc86..3359bdc9f 100644
--- a/share/html/Prefs/AboutMe.html
+++ b/share/html/Prefs/AboutMe.html
@@ -50,7 +50,7 @@
<& /Elements/ListActions, actions => \@results &>
-<form action="<%RT->Config->Get('WebPath')%>/Prefs/AboutMe.html" method="post">
+<form action="<%RT->Config->Get('WebPath')%>/Prefs/AboutMe.html" method="post" enctype="multipart/form-data">
<input type="hidden" class="hidden" name="id" value="<%$UserObj->Id%>" />
<table width="100%" border="0">
diff --git a/share/static/css/base/lifecycleui-editor.css b/share/static/css/base/lifecycleui-editor.css
index 732a82043..25c1cfec8 100644
--- a/share/static/css/base/lifecycleui-editor.css
+++ b/share/static/css/base/lifecycleui-editor.css
@@ -1,15 +1,13 @@
.lifecycle-ui {
- margin-left: 10%;
}
.lifecycle-ui.editing svg {
- float: left;
- width: 60%;
+ width: 100%;
height: 500px;
}
.lifecycle-ui.editing .overlay-buttons {
- left: 50%;
+ left: 85%;
}
.lifecycle-ui .inspector {
diff --git a/share/static/css/base/lifecycleui-viewer.css b/share/static/css/base/lifecycleui-viewer.css
index d9805e079..4739e0b1c 100644
--- a/share/static/css/base/lifecycleui-viewer.css
+++ b/share/static/css/base/lifecycleui-viewer.css
@@ -112,6 +112,7 @@
.lifecycle-ui .lifecycle-view {
position: relative;
+ display: inline-block;
}
.lifecycle-ui .overlay-buttons button {
diff --git a/share/static/js/lifecycleui-editor.js b/share/static/js/lifecycleui-editor.js
index 887015395..65b8783a7 100644
--- a/share/static/js/lifecycleui-editor.js
+++ b/share/static/js/lifecycleui-editor.js
@@ -1,10 +1,20 @@
jQuery(function () {
- RT.Editor = class LifecycleEditor extends RT.LifecycleViewer {
+ RT.Editor = class LifecycleEditor {
constructor(container, name, config, ticketStatus) {
- super( container, name, config, ticketStatus );
-
+ self.container = container;
+ self.name = name;
+ self.config = config;
+ self.ticketStatus = ticketStatus;
this.pointHandleRadius = 5;
+
+ 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
}
initializeEditor(node, name, config, focusStatus) {
@@ -25,24 +35,32 @@ jQuery(function () {
self.addNewStatus()
self.renderDisplay();
});
+ d3.select("body").on("keydown", function () {
+ if ( self._focusItem ) {
+ if ( d3.event.keyCode == 68 || d3.event.keyCode == 46 ) {
+ RT.Lifecycle.deleteStatus(self._focusItem._key);
+ self.renderDisplay();
+ }
+ self.defocus();
+ }
+ })
}
- clickedStatus(d, p_el) {
+ clickedStatus(d) {
self = this;
- var circle = d3.select(p_el).select('circle')._groups[0][0];
+ let g = d3.select(d3.select('#key-'+d._key)._groups[0][0]);
+ let circle = d3.select(g)._groups[0][0].select('circle');
let current_val = d.name;
d.name = '';
self.renderDisplay();
- var frm = d3.select(p_el).append("foreignObject");
-
- var circle_d3 = d3.select(circle);
+ var frm = g.append("foreignObject");
var inp = frm
- .attr("x", circle_d3.attr('cx') - circle_d3.attr('r') + 10)
- .attr("y", circle_d3.attr('cy') - circle_d3.attr('r') / 2 + 4)
- .attr("width", circle_d3.attr('r') * 2)
+ .attr("x", Number(circle.attr('cx')) - Number(circle.attr('r')) / 2)
+ .attr("y", Number(circle.attr('cy')) - Number(circle.attr('r')) / 4)
+ .attr("width", 200)
.attr("height", 20)
.append("xhtml:form")
.append("input")
@@ -110,18 +128,6 @@ jQuery(function () {
this.lifecycle.createStatus(p[0], p[1]);
}
-
-
-
-
-
-
-
-
-
-
-
-
_refreshLifecycleUI(refreshContent) {
var self = this;
var lifecycle = self.lifecycle;
@@ -173,12 +179,6 @@ jQuery(function () {
});
}
- // add rects under text decorations for highlighting
- // renderTextDecorations(initial) {
- // Super.prototype.renderTextDecorations.call(this, initial);
- // var self = this;
- // self.renderTextDecorationBackgrounds(initial);
- // }
renderTextDecorationBackgrounds(initial) {
var self = this;
var rects = self.decorationContainer.selectAll("rect.text-background")
@@ -225,6 +225,7 @@ jQuery(function () {
if (this.inspectorNode && this.inspectorNode._key != d._key) {
return;
}
+
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)) {
@@ -243,7 +244,7 @@ jQuery(function () {
.subject(function (d) { return { x: self.xScale(d.x), y: self.yScale(d.y) }; })
.on("start", function (d) { self.didBeginDrag(d, this); })
.on("drag", function (d) { self.didDragItem(d, this); })
- .on("end", function (d) { self.didEndDrag(d, this); });
+ .on("end", function (d) { self.didEndDrag(d, this); })
}
didEnterStatusNodes(statuses) {
statuses.call(this._createDrag());
@@ -254,5 +255,487 @@ jQuery(function () {
didEnterLineDecorations(lines) {
lines.call(this._createDrag());
}
- };
+
+
+
+
+ // View
+ 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);
+ });
+ };
+ }
+
+
});
diff --git a/share/static/js/lifecycleui-model.js b/share/static/js/lifecycleui-model.js
index 74c37c3a3..ca3f1abe6 100644
--- a/share/static/js/lifecycleui-model.js
+++ b/share/static/js/lifecycleui-model.js
@@ -16,6 +16,15 @@ jQuery(function () {
this._undoState = { undoStack: [], redoStack: [] };
this._keyMap = {};
this._statusMeta = {};
+
+ // Viewer
+ 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
}
initializeFromConfig(config) {
var self = this;
@@ -686,8 +695,13 @@ jQuery(function () {
}
return jQuery.unique(rights.sort());
}
- }
-;
+ };
+
+
+
+
+
+
Lifecycle.prototype._initialPointsForPolygon = {
Line: [
@@ -706,7 +720,6 @@ jQuery(function () {
{x: -600, y: 600}
]
};
-
RT.Lifecycle = new Lifecycle();
});
diff --git a/share/static/js/lifecycleui-viewer.js b/share/static/js/lifecycleui-viewer.js
index 93b725238..ce68fd86a 100644
--- a/share/static/js/lifecycleui-viewer.js
+++ b/share/static/js/lifecycleui-viewer.js
@@ -90,14 +90,21 @@ jQuery(function () {
.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.clickedStatus(d, this);
+
+ self.focusItem(d)
+ })
+ newStatuses.append("text")
+ .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8)
+ .on("click", function (d) {
+ d3.event.stopPropagation();
+ self.clickedStatus(d);
})
- .call(function (statuses) { self.didEnterStatusNodes(statuses); });
- newStatuses.append("circle")
- .attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8);
- newStatuses.append("text");
if (!initial) {
newStatuses.transition().duration(200 * self.animationFactor)
.select("circle")
@@ -472,6 +479,10 @@ jQuery(function () {
e.preventDefault();
self.zoomScale(.75, true);
});
+ self.container.on('click', 'button.zoom-reset', function (e) {
+ e.preventDefault();
+ self.resetZoom(true);
+ });
}
};
commit 6ac265b756942c18ea553ed8684904af74125842
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Oct 18 17:31:56 2019 -0400
Remove extra componenets and make Viewer/Editor one file
diff --git a/share/html/Elements/Lifecycle/Graph b/share/html/Elements/Lifecycle/Graph
index 99f0e4814..e4d617578 100644
--- a/share/html/Elements/Lifecycle/Graph
+++ b/share/html/Elements/Lifecycle/Graph
@@ -47,13 +47,7 @@
%# END BPS TAGGED BLOCK }}}
<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 zoom-in">+</button>
- <button class="zoom zoom-reset">0</button>
- <button class="zoom zoom-out">-</button>
- </div>
<svg>
- <& GraphExtras, %ARGS &>
<g class="transform">
<g class="decorations"></g>
<g class="transitions"></g>
@@ -81,7 +75,6 @@
var editor = new RT.Editor( container, name, config, ticketStatus );
editor.initializeEditor(container, name, config, ticketStatus);
- editor.initializeViewer(container, name, config, ticketStatus);
});
});
</script>
diff --git a/share/static/css/base/lifecycleui-editor.css b/share/static/css/base/lifecycleui-editor.css
index 25c1cfec8..8fa65dbae 100644
--- a/share/static/css/base/lifecycleui-editor.css
+++ b/share/static/css/base/lifecycleui-editor.css
@@ -143,3 +143,16 @@
.lifecycle-ui .inspector tr.section td {
padding-top: 1em;
}
+
+
+
+
+
+/* NEW */
+g text {
+ cursor: text;
+}
+
+g circle.node-selected {
+ fill: #98b9eb !important;
+ }
\ No newline at end of file
diff --git a/share/static/js/lifecycleui-editor.js b/share/static/js/lifecycleui-editor.js
index 65b8783a7..4841f0f78 100644
--- a/share/static/js/lifecycleui-editor.js
+++ b/share/static/js/lifecycleui-editor.js
@@ -19,7 +19,26 @@ jQuery(function () {
initializeEditor(node, name, config, focusStatus) {
var self = this;
- self.initializeViewer(node, name, config, focusStatus);
+
+ 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);
+
self.container.closest('form[name=ModifyLifecycle]').submit(function (e) {
var config = self.lifecycle.exportAsConfiguration();
var form = jQuery(this);
@@ -44,6 +63,7 @@ jQuery(function () {
self.defocus();
}
})
+ self.renderDisplay();
}
clickedStatus(d) {
@@ -54,6 +74,8 @@ jQuery(function () {
let current_val = d.name;
d.name = '';
+ // Defocus so that a 'd' key doesn't delete our node
+ self.defocus();
self.renderDisplay();
var frm = g.append("foreignObject");
@@ -119,8 +141,8 @@ jQuery(function () {
}
viewportCenterPoint() {
var rect = this.svg.node().getBoundingClientRect();
- var x = (rect.width / 2 - this._currentZoom.x) / this._currentZoom.k;
- var y = (rect.height / 2 - this._currentZoom.y) / this._currentZoom.k;
+ var x = 0;
+ var y = 0;
return [this.xScaleInvert(x), this.yScaleInvert(y)];
}
addNewStatus() {
@@ -201,20 +223,6 @@ jQuery(function () {
.style("opacity", 1)
.on("end", function () { d3.select(this).style("opacity", undefined); });
}
- newRects.merge(rects)
- .classed("focus", function (d) { return self.isFocused(d); })
- .each(function (d) {
- var rect = d3.select(this);
- var label = self.decorationContainer.select("text[data-key='" + d._key + "']");
- var bbox = label.node().getBoundingClientRect();
- var width = bbox.width / self._currentZoom.k;
- var height = bbox.height / self._currentZoom.k;
- var padding = 5 / self._currentZoom.k;
- rect.attr("x", self.xScale(d.x) - padding)
- .attr("y", self.yScale(d.y) - padding)
- .attr("width", width + padding * 2)
- .attr("height", height + padding * 2);
- });
}
didBeginDrag(d, node) { }
@@ -274,61 +282,13 @@ jQuery(function () {
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) { }
+
+ // Should we get rid of these
didEnterTransitions(paths) { }
- didEnterTextDecorations(labels) { }
didEnterPolygonDecorations(polygons) { }
didEnterCircleDecorations(circles) { }
didEnterLineDecorations(lines) { }
+
renderStatusNodes(initial) {
var self = this;
var statuses = self.statusContainer.selectAll("g")
@@ -354,6 +314,7 @@ jQuery(function () {
.attr("r", initial ? self.statusCircleRadius : self.statusCircleRadius * .8)
.on("click", function (d) {
d3.event.stopPropagation();
+
self.clickedStatus(d);
})
if (!initial) {
@@ -375,9 +336,6 @@ jQuery(function () {
.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) {
@@ -613,24 +571,26 @@ jQuery(function () {
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;
+ if ( this._focusItem ) {
+ // TODO Make this abstracted
+ let g = d3.select(d3.select('#key-'+this._focusItem._key)._groups[0][0]);
+ let circle = d3.select(g)._groups[0][0].select('circle');
+
+ circle.classed("node-selected", false);
+ }
this.svg.classed("has-focus", false)
.attr('data-focus-type', undefined);
+ this._focusItem = null;
}
focusItem(d) {
this.defocus();
this._focusItem = d;
- this.svg.classed("has-focus", true)
- .attr('data-focus-type', d._type);
+
+ let g = d3.select(d3.select('#key-'+d._key)._groups[0][0]);
+ let circle = d3.select(g)._groups[0][0].select('circle');
+
+ circle.classed("node-selected", true);
}
focusOnStatus(statusName, center, animated) {
if (!statusName) {
@@ -682,60 +642,6 @@ jQuery(function () {
}
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);
- });
- };
}
-
});
-----------------------------------------------------------------------
More information about the rt-commit
mailing list