mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-25 08:21:05 -06:00
Add creating a segment using mouse
Create new semicircles around the node after mouseover. These work as buttons to create arrow and after clicking on another node the Add topology segment dialog is opened. Also selecting segment works, if the segment already exists then the segment is selected instead of opening the dialog. https://fedorahosted.org/freeipa/ticket/5648 Reviewed-By: Petr Vobornik <pvoborni@redhat.com>
This commit is contained in:
parent
94909d21db
commit
be235cedf8
@ -150,42 +150,61 @@ tbody:empty { display: none; }
|
||||
|
||||
.topology-view {
|
||||
svg {
|
||||
background-color: #FFF;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
background-color: #FFF;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
path.link {
|
||||
fill: none;
|
||||
stroke-width: 4px;
|
||||
cursor: pointer;
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-width: 4px;
|
||||
cursor: pointer;
|
||||
.dragline {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.plus {
|
||||
font-size: .9em;
|
||||
font-family: FontAwesome;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.adder_label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
path.adder {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected {
|
||||
stroke-dasharray: 10,2;
|
||||
}
|
||||
|
||||
.marker {
|
||||
stroke: rgba(0, 0, 0);
|
||||
}
|
||||
|
||||
path.link.selected {
|
||||
stroke-dasharray: 10,2;
|
||||
}
|
||||
|
||||
circle.node {
|
||||
stroke-width: 1.5px;
|
||||
cursor: pointer;
|
||||
stroke-width: 1.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
text {
|
||||
font: 16px sans-serif;
|
||||
pointer-events: none;
|
||||
font: 16px sans-serif;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
text.id {
|
||||
text-anchor: middle;
|
||||
font-weight: bold;
|
||||
text-anchor: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,13 +29,21 @@ var topology_graph = {
|
||||
topology_graph.TopoGraph = declare([Evented], {
|
||||
width: 960,
|
||||
height: 500,
|
||||
_adder_anim_duration: 200,
|
||||
_adder_inner_radius: 15,
|
||||
_adder_outer_radius: 30,
|
||||
_colors: d3.scale.category10(),
|
||||
_svg : null,
|
||||
_path: null,
|
||||
_circle: null,
|
||||
|
||||
_create_agreement: null,
|
||||
_selected_link: null,
|
||||
_mousedown_link: null,
|
||||
_source_node: null,
|
||||
_source_node_html: null,
|
||||
_target_node: null,
|
||||
_drag_line: null,
|
||||
|
||||
/**
|
||||
* Nodes - IPA servers
|
||||
@ -115,31 +123,89 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
node.x = Number(this._get_local_storage_attr(node.id, 'x'));
|
||||
node.y = Number(this._get_local_storage_attr(node.id, 'y'));
|
||||
}
|
||||
node.ca_adder = d3.svg.arc()
|
||||
.innerRadius(this._adder_inner_radius)
|
||||
.outerRadius(this._adder_inner_radius)
|
||||
.startAngle(2 * (Math.PI/180))
|
||||
.endAngle(178 * (Math.PI/180));
|
||||
|
||||
node.domain_adder = d3.svg.arc()
|
||||
.innerRadius(this._adder_inner_radius)
|
||||
.outerRadius(this._adder_inner_radius)
|
||||
.startAngle(182 * (Math.PI/180))
|
||||
.endAngle(358 * (Math.PI/180));
|
||||
|
||||
node.drag_mode = false;
|
||||
}
|
||||
|
||||
this._init_layout();
|
||||
this._define_shapes();
|
||||
|
||||
// handles to link and node element groups
|
||||
// the order of adding shapes is important because of order of showing
|
||||
// them
|
||||
this._path = this._svg.append('svg:g')
|
||||
.classed('shapes', true)
|
||||
.selectAll('path');
|
||||
|
||||
this._drag_line = this._svg.append('svg:g')
|
||||
.classed('shapes', true)
|
||||
.append('path')
|
||||
.style('marker-end', 'url(#end-arrow)')
|
||||
.attr('class', 'link dragline hidden')
|
||||
.attr('d', 'M0,0L0,0')
|
||||
.on('click', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
|
||||
this._create_agreement = false;
|
||||
this.reset_mouse_vars();
|
||||
|
||||
this._drag_line
|
||||
.classed('hidden', true)
|
||||
.style('marker-end', '');
|
||||
|
||||
this.restart();
|
||||
|
||||
}.bind(this));
|
||||
|
||||
this._circle = this._svg.append('svg:g')
|
||||
.classed('shapes', true)
|
||||
.selectAll('g');
|
||||
|
||||
this._selected_link = null;
|
||||
this._mouseup_node = null;
|
||||
this._mousedown_link = null;
|
||||
this._selected_node = null;
|
||||
this._source_node = null;
|
||||
this._target_node = null;
|
||||
this.restart();
|
||||
},
|
||||
|
||||
_create_svg: function(container) {
|
||||
var self = this;
|
||||
|
||||
this._svg = d3.select(container[0]).
|
||||
append('svg').
|
||||
attr('width', this.width).
|
||||
attr('height', this.height);
|
||||
attr('height', this.height).
|
||||
on('mousemove', mousemove);
|
||||
|
||||
function mousemove(d) {
|
||||
if (!self._source_node && !self._create_agreement) return;
|
||||
|
||||
var translate = self._get_stored_transformation();
|
||||
var x = self._source_node.x;
|
||||
var y = self._source_node.y;
|
||||
|
||||
var mouse_x = x + d3.mouse(self._source_node_html)[0];
|
||||
var mouse_y = y + d3.mouse(self._source_node_html)[1];
|
||||
|
||||
// update drag line
|
||||
self._drag_line.attr('d', 'M' + x + ',' + y + 'L' + mouse_x + ',' + mouse_y);
|
||||
|
||||
self.restart();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_init_layout: function() {
|
||||
@ -342,10 +408,27 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
this._append_suffix_hint(suffix, x, y);
|
||||
y += 30;
|
||||
}
|
||||
|
||||
this._circle_color = this._colors(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns lenght of string with set class in pixels
|
||||
*/
|
||||
_count_string_size: function(str, cls) {
|
||||
if (!str) return 0;
|
||||
|
||||
cls = cls || '';
|
||||
|
||||
var node = this._svg.append('text')
|
||||
.classed(cls, true)
|
||||
.text(str);
|
||||
|
||||
var length = node.node().getComputedTextLength();
|
||||
|
||||
node.remove();
|
||||
|
||||
return length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Restart the simulation to reflect changes in data/state
|
||||
*/
|
||||
@ -422,6 +505,8 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
.on("dragend", dragended);
|
||||
|
||||
function dragstarted(d) {
|
||||
d.drag_mode = true;
|
||||
hide_semicircles.bind(this, d)();
|
||||
d3.event.sourceEvent.stopPropagation();
|
||||
// Store the original value of fixed and set the node fixed.
|
||||
d.fixed = d.fixed << 1;
|
||||
@ -437,11 +522,213 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
}
|
||||
|
||||
function dragended(d) {
|
||||
d.drag_mode = false;
|
||||
// Restore old value of fixed.
|
||||
d.fixed = d.fixed >> 1;
|
||||
self._layout.resume();
|
||||
}
|
||||
|
||||
function add_labels(type, color, adder_group) {
|
||||
var label_radius = 3;
|
||||
|
||||
var plus = adder_group
|
||||
.append('text')
|
||||
.classed('plus', true)
|
||||
.classed(type + '_plus', true)
|
||||
.text('\uf067');
|
||||
|
||||
var label = adder_group.append('path')
|
||||
.attr('id', type + '_label');
|
||||
|
||||
if (type === 'ca') {
|
||||
plus.attr('dx', '18')
|
||||
.attr('dy', '4');
|
||||
var adder_label = adder_group.append('text')
|
||||
.append('textPath')
|
||||
.classed('adder_label', true)
|
||||
.style('fill', color)
|
||||
.attr('xlink:href', '#' + type + '_label')
|
||||
.text(type);
|
||||
|
||||
var str_size = self._count_string_size(type, 'adder_label');
|
||||
var str_translate = str_size + self._adder_outer_radius + 3;
|
||||
label.attr('d', 'M 33 3 L ' + str_translate + ' 3');
|
||||
|
||||
adder_group.insert('rect', 'text')
|
||||
.attr('x', '33')
|
||||
.attr('y', '-11')
|
||||
.attr('rx', label_radius)
|
||||
.attr('ry', label_radius)
|
||||
.attr('width', str_size)
|
||||
.attr('height', '18')
|
||||
.style("fill", "white");
|
||||
}
|
||||
else {
|
||||
plus.attr('dx', '-26')
|
||||
.attr('dy', '4');
|
||||
adder_label = adder_group.append('text')
|
||||
.append('textPath')
|
||||
.classed('adder_label', true)
|
||||
.style('fill', color)
|
||||
.attr('xlink:href', '#' + type + '_label')
|
||||
.text(type);
|
||||
|
||||
str_size = self._count_string_size(type, 'adder_label');
|
||||
str_translate = str_size + self._adder_outer_radius + 3;
|
||||
label.attr('d', 'M -' + str_translate + ' 3 L -33 3');
|
||||
|
||||
adder_group.insert('rect', 'text')
|
||||
.attr('x', '-'+str_translate)
|
||||
.attr('y', '-11')
|
||||
.attr('rx', label_radius)
|
||||
.attr('ry', label_radius)
|
||||
.attr('width', str_size)
|
||||
.attr('height', '18')
|
||||
.style('fill', 'white');
|
||||
}
|
||||
}
|
||||
|
||||
function create_semicircle(d, type) {
|
||||
var color = d3.rgb(self._colors(type)).toString();
|
||||
var adder_group = d3.select(this).select('g');
|
||||
var scale = '1.05';
|
||||
|
||||
adder_group.append("path")
|
||||
.classed(type+'_adder', true)
|
||||
.classed('adder', true)
|
||||
.attr("d", d[type + '_adder'])
|
||||
.attr("fill", color)
|
||||
.on('mouseover', function(d) {
|
||||
window.clearTimeout(d._timeout_hide);
|
||||
|
||||
d3.select(this).attr('transform', 'scale('+scale+')');
|
||||
adder_group.select('text.' + type + '_plus')
|
||||
.attr('transform', 'scale('+scale+')');
|
||||
})
|
||||
.on('mouseout', function(d) {
|
||||
d3.select(this).attr('transform', '');
|
||||
adder_group.select('text.' + type + '_plus')
|
||||
.attr('transform', '');
|
||||
})
|
||||
.on('click', function(d) {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
self.emit('link-selected', { link: null });
|
||||
|
||||
hide_semicircles.bind(this, d)();
|
||||
|
||||
// select node
|
||||
if (!self._source_node) {
|
||||
self._source_node = d;
|
||||
self._source_node_html = d3.select(this)
|
||||
.select('circle').node();
|
||||
self._create_agreement = true;
|
||||
}
|
||||
|
||||
self._selected_link = null;
|
||||
|
||||
var translate = self._get_stored_transformation();
|
||||
var x = self._source_node.x;
|
||||
var y = self._source_node.y;
|
||||
|
||||
// add position of node + translation of whole graph + relative
|
||||
// position of the mouse
|
||||
var mouse_x = d.x + d3.mouse(this)[0];
|
||||
var mouse_y = d.y + d3.mouse(this)[1];
|
||||
|
||||
// reposition drag line
|
||||
self._drag_line
|
||||
.style('marker-end', 'url(#' + type + '-end-arrow)')
|
||||
.style('stroke', color)
|
||||
.classed('hidden', false)
|
||||
.attr('suffix', type)
|
||||
.attr('d', 'M' + x + ',' + y +
|
||||
'L' + mouse_x + ',' + mouse_y);
|
||||
|
||||
self.restart();
|
||||
}.bind(this))
|
||||
.on('mousedown.drag', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
})
|
||||
.transition()
|
||||
.duration(self._adder_anim_duration)
|
||||
.attr("d", d[type + '_adder']
|
||||
.outerRadius(self._adder_outer_radius))
|
||||
.each('end', function() {
|
||||
add_labels(type, color, adder_group);
|
||||
});
|
||||
}
|
||||
|
||||
function show_semicircles(d) {
|
||||
|
||||
if(!d3.select(this).select('g path').empty()) return;
|
||||
|
||||
if (!d.drag_mode && !self._create_agreement) {
|
||||
|
||||
// append invisible circle which covers spaces between node
|
||||
// and adders it prevents hiding adders when mouse is on the space
|
||||
d3.select(this).append('g')
|
||||
.append('circle')
|
||||
.attr('r', self._adder_outer_radius)
|
||||
.style('opacity', 0);
|
||||
create_semicircle.bind(this, d, 'ca')();
|
||||
|
||||
create_semicircle.bind(this, d, 'domain')();
|
||||
|
||||
//move the identification text
|
||||
d3.select(this).select('text')
|
||||
.transition()
|
||||
.duration(self._adder_anim_duration)
|
||||
.attr('dy', '45');
|
||||
}
|
||||
}
|
||||
|
||||
function hide_semicircles(d) {
|
||||
var curr_nod = d3.select(this);
|
||||
curr_nod.selectAll('.plus,.adder_label,rect')
|
||||
.transition()
|
||||
.ease('exp')
|
||||
.duration(100)
|
||||
.style('font-size', '0px')
|
||||
.remove();
|
||||
curr_nod.select('path.domain_adder')
|
||||
.transition()
|
||||
.attr("d", d.domain_adder
|
||||
.outerRadius(self._adder_inner_radius))
|
||||
.duration(self._adder_anim_duration);
|
||||
curr_nod.select('path.ca_adder')
|
||||
.transition()
|
||||
.attr("d", d.ca_adder
|
||||
.outerRadius(self._adder_inner_radius))
|
||||
.duration(self._adder_anim_duration);
|
||||
curr_nod.select('g')
|
||||
.transition()
|
||||
.duration(self._adder_anim_duration)
|
||||
.remove();
|
||||
curr_nod.select('text')
|
||||
.transition()
|
||||
.attr('dy', '30')
|
||||
.duration(self._adder_anim_duration);
|
||||
}
|
||||
|
||||
function is_suffix_shown(suffix) {
|
||||
var links = self._source_node.targets[self._target_node.id];
|
||||
|
||||
if (!links) return false;
|
||||
|
||||
for (var i=0, l=links.length; i<l; i++) {
|
||||
var link = links[i];
|
||||
if (link.suffix.cn[0] === suffix) {
|
||||
self._selected_link = link;
|
||||
self.emit('link-selected', { link: link });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// add new nodes
|
||||
var g = this._circle.enter()
|
||||
.append('svg:g')
|
||||
@ -453,6 +740,49 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
d.fixed = d.fixed ^ 1;
|
||||
self._layout.resume();
|
||||
})
|
||||
.on('mouseover', function(d) {
|
||||
window.clearTimeout(d._timeout_hide);
|
||||
show_semicircles.bind(this, d)();
|
||||
d3.select('circle.cover').classed('cover', true);
|
||||
})
|
||||
.on('mouseout', function(d) {
|
||||
d._timeout_hide = window.setTimeout(hide_semicircles
|
||||
.bind(this, d), 50);
|
||||
})
|
||||
.on('click', function(d) {
|
||||
if (!self._create_agreement) return;
|
||||
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
|
||||
if (self._source_node !== d) {
|
||||
self._target_node = d;
|
||||
var source = self._source_node;
|
||||
var target = self._target_node;
|
||||
var suffix = self._drag_line.attr('suffix');
|
||||
var direction = 'left';
|
||||
var link = {
|
||||
source: source,
|
||||
target: target,
|
||||
suffix: suffix,
|
||||
left: false,
|
||||
right: false
|
||||
};
|
||||
|
||||
if (!is_suffix_shown(suffix)) {
|
||||
link[direction] = true;
|
||||
self.emit('add-agreement', link);
|
||||
}
|
||||
}
|
||||
|
||||
self._drag_line
|
||||
.classed('hidden', true)
|
||||
.attr('suffix', '')
|
||||
.style('marker-end', '');
|
||||
|
||||
self.restart();
|
||||
self.reset_mouse_vars();
|
||||
})
|
||||
.call(drag);
|
||||
|
||||
g.append('svg:circle')
|
||||
@ -488,6 +818,15 @@ topology_graph.TopoGraph = declare([Evented], {
|
||||
.attr("transform", transform);
|
||||
},
|
||||
|
||||
reset_mouse_vars: function() {
|
||||
this._source_node = null;
|
||||
this._source_node_html = null;
|
||||
this._target_node = null;
|
||||
this._mousedown_link = null;
|
||||
this._create_agreement = null;
|
||||
|
||||
},
|
||||
|
||||
resize: function(height, width) {
|
||||
if (!(isNaN(height) || isNaN(width))) {
|
||||
this.height = height < 0 ? 0 : height;
|
||||
|
Loading…
Reference in New Issue
Block a user