ui: Improved Discovery Chain resizing (#9360)

* install on-resize modifier

* Rerrange things to use on-resize modifier for positioning
This commit is contained in:
John Cowen 2020-12-11 09:38:33 +00:00 committed by GitHub
parent f827deb8a7
commit eb85b858d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 62 deletions

View File

@ -16,6 +16,7 @@
} }
{{/if}} {{/if}}
</style> </style>
<div class="routes"> <div class="routes">
<header> <header>
<h2> <h2>
@ -29,12 +30,14 @@
<div role="group"> <div role="group">
{{#each routes as |item|}} {{#each routes as |item|}}
<Consul::DiscoveryChain::RouteCard <Consul::DiscoveryChain::RouteCard
{{on-resize (dom-position (set item 'rect') from=this.edges)}}
@item={{item}} @item={{item}}
@onclick={{action "click"}} @onclick={{action "click"}}
/> />
{{/each}} {{/each}}
</div> </div>
</div> </div>
<div class="splitters"> <div class="splitters">
<header> <header>
<h2> <h2>
@ -48,102 +51,126 @@
<div role="group"> <div role="group">
{{#each (sort-by 'Name' splitters) as |item|}} {{#each (sort-by 'Name' splitters) as |item|}}
<Consul::DiscoveryChain::SplitterCard <Consul::DiscoveryChain::SplitterCard
{{on-resize (dom-position (set item 'rect') from=this.edges)}}
@item={{item}} @item={{item}}
@onclick={{action "click"}} @onclick={{action "click"}}
/> />
{{/each}} {{/each}}
</div> </div>
</div> </div>
<div class="resolvers"> <div class="resolvers">
<header> <header>
<h2> <h2>
Resolvers Resolvers
<span {{tooltip "Resolvers are used to define which instances of a service should satisfy discovery requests."}}> <span
{{tooltip "Resolvers are used to define which instances of a service should satisfy discovery requests."}}
>
</span> </span>
</h2> </h2>
</header> </header>
<div role="group"> <div role="group">
{{#each (sort-by 'Name' resolvers) as |item|}} {{#each (sort-by 'Name' resolvers) as |item|}}
<Consul::DiscoveryChain::ResolverCard <Consul::DiscoveryChain::ResolverCard
{{on-resize (dom-position (set item 'rect') from=this.edges)}}
@item={{item}} @item={{item}}
@edges={{this.edges}}
@onclick={{action "click"}} @onclick={{action "click"}}
/> />
{{/each}} {{/each}}
</div> </div>
</div> </div>
{{nodes}}
<svg class="edges" <svg class="edges"
{{did-insert (set this 'edges')}}
width="100%" width="100%"
height="100%" height="100%"
viewBox={{concat '0 0 ' width ' ' height}}
preserveAspectRatio="none" preserveAspectRatio="none"
> >
{{#each routes as |item|}} {{#each routes as |item|}}
{{#let (dom-position (concat '#' item.ID) '.edges') as |src|}} {{#if item.rect}}
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |destRect|}} {{#let item.rect item.NextItem.rect as |src destRect|}}
{{#let (tween-to (hash {{#let (tween-to (hash
x=destRect.x x=destRect.x
y=(add destRect.y (div destRect.height 2)) y=(add destRect.y (div destRect.height 2))
) (concat item.ID)) as |dest|}} ) (concat item.ID)) as |dest|}}
<path
id={{concat item.ID '>' item.NextNode}}
d={{
svg-curve (hash
x=dest.x
y=dest.y
) src=(hash
x=(add src.x src.width)
y=(add src.y (div src.height 2))
)}} />
{{/let}}
{{/let}}
{{/let}}
{{/each}}
{{#each splitters as |splitter|}}
{{#let (dom-position (concat '#' splitter.ID) '.edges') as |src|}}
{{#each splitter.Splits as |item index|}}
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |destRect|}}
{{#let (tween-to (hash
x=destRect.x
y=(add destRect.y (div destRect.height 2))
) (concat splitter.ID '-' index)) as |dest|}}
<path <path
{{tooltip (concat (round (or item.Weight 0) decimals=2) '%') options=(hash followCursor=true)}} id={{concat item.ID '>' item.NextNode}}
id={{concat 'splitter:' splitter.Name '>' item.NextNode}}
class="split"
d={{ d={{
svg-curve (hash svg-curve (hash
x=dest.x x=dest.x
y=dest.y y=(sub dest.y 0)
) src=(hash ) src=(hash
x=(add src.x src.width) x=src.right
y=(add src.y (div src.height 2)) y=(add src.y (div src.height 2))
)}} /> )}}
/>
{{/let}} {{/let}}
{{/let}} {{/let}}
{{/each}} {{/if}}
{{/let}}
{{/each}} {{/each}}
{{#each splitters as |splitter|}}
{{#if splitter.rect}}
{{#let splitter.rect as |src|}}
{{#each splitter.Splits as |item index|}}
{{#let item.NextItem.rect as |destRect|}}
{{#let (tween-to (hash
x=destRect.x
y=(add destRect.y (div destRect.height 2))
) (concat splitter.ID '-' index)) as |dest|}}
<path
{{tooltip
(concat (round (or item.Weight 0) decimals=2) '%')
options=(hash followCursor=true)
}}
id={{concat 'splitter:' splitter.Name '>' item.NextNode}}
class="split"
d={{
svg-curve (hash
x=dest.x
y=dest.y
) src=(hash
x=src.right
y=(add src.y (div src.height 2))
)}}
/>
{{/let}}
{{/let}}
{{/each}}
{{/let}}
{{/if}}
{{/each}}
</svg> </svg>
<svg class="resolver-inlets" viewBox={{concat '0 0 10 ' height}}>
<svg class="resolver-inlets" height="100%">
{{#each routes as |item|}} {{#each routes as |item|}}
{{#if (string-starts-with item.NextNode 'resolver:') }} {{#if (string-starts-with item.NextNode 'resolver:') }}
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}} {{#let (or item.NextItem.rect (hash y=0 height=0)) as |dest|}}
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} /> <circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
{{/let}} {{/let}}
{{/if}} {{/if}}
{{/each}} {{/each}}
{{#each splitters as |item|}} {{#each splitters as |item|}}
{{#each item.Splits as |item|}} {{#each item.Splits as |item|}}
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}} {{#let (or item.NextItem.rect (hash y=0 height=0)) as |dest|}}
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} /> <circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
{{/let}} {{/let}}
{{/each}} {{/each}}
{{/each}} {{/each}}
</svg> </svg>
<svg class="splitter-inlets" viewBox={{concat '0 0 10 ' height}}>
<svg class="splitter-inlets" height="100%">
{{#each routes as |item|}} {{#each routes as |item|}}
{{#if (string-starts-with item.NextNode 'splitter:') }} {{#if (string-starts-with item.NextNode 'splitter:') }}
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}} {{#let (or item.NextItem.rect (hash y=0 height=0)) as |dest|}}
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} /> <circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
{{/let}} {{/let}}
{{/if}} {{/if}}

View File

@ -73,6 +73,37 @@ export default Component.extend({
} }
return routes; return routes;
}), }),
nodes: computed('routes', 'splitters', 'resolvers', function() {
let nodes = this.resolvers.reduce((prev, item) => {
prev[`resolver:${item.ID}`] = item;
item.Children.reduce((prev, item) => {
prev[`resolver:${item.ID}`] = item;
return prev;
}, prev);
return prev;
}, {});
nodes = this.splitters.reduce((prev, item) => {
prev[item.ID] = item;
return prev;
}, nodes);
nodes = this.routes.reduce((prev, item) => {
prev[item.ID] = item;
return prev;
}, nodes);
Object.entries(nodes).forEach(([key, value]) => {
if (typeof value.NextNode !== 'undefined') {
value.NextItem = nodes[value.NextNode];
}
if (typeof value.Splits !== 'undefined') {
value.Splits.forEach(item => {
if (typeof item.NextNode !== 'undefined') {
item.NextItem = nodes[item.NextNode];
}
});
}
});
return '';
}),
resolvers: computed('chain.{Nodes,Targets}', function() { resolvers: computed('chain.{Nodes,Targets}', function() {
return getResolvers( return getResolvers(
this.chain.Datacenter, this.chain.Datacenter,
@ -117,12 +148,6 @@ export default Component.extend({
edges: edges.map(item => `#${CSS.escape(item)}`), edges: edges.map(item => `#${CSS.escape(item)}`),
}; };
}), }),
width: computed('chain.{Nodes,Targets}', function() {
return this.element.offsetWidth;
}),
height: computed('chain.{Nodes,Targets}', function() {
return this.element.offsetHeight;
}),
actions: { actions: {
click: function(e) { click: function(e) {
const id = e.currentTarget.getAttribute('id'); const id = e.currentTarget.getAttribute('id');

View File

@ -1,8 +1,9 @@
<div <div
class="resolver-card" class="resolver-card"
...attributes
> >
<header onclick={{optional @onclick}} id={{concat 'resolver:' @item.ID}}> <header
...attributes
onclick={{optional @onclick}} id={{concat 'resolver:' @item.ID}}>
<a name=""> <a name="">
<h3>{{@item.Name}}</h3> <h3>{{@item.Name}}</h3>
{{#if item.Failover}} {{#if item.Failover}}
@ -28,7 +29,11 @@
{{#if (gt @item.Children.length 0)}} {{#if (gt @item.Children.length 0)}}
<ul> <ul>
{{#each @item.Children as |child|}} {{#each @item.Children as |child|}}
<li onclick={{optional @onclick}} id={{concat 'resolver:' child.ID}}> <li
onclick={{optional @onclick}}
id={{concat 'resolver:' child.ID}}
{{on-resize (dom-position (set child 'rect') from=@edges)}}
>
<a name=""> <a name="">
{{#if child.Redirect}} {{#if child.Redirect}}
<dl class="redirect"> <dl class="redirect">

View File

@ -2,6 +2,7 @@
class="route-card" class="route-card"
onclick={{@onclick}} onclick={{@onclick}}
id={{@item.ID}} id={{@item.ID}}
...attributes
> >
<header class={{if (eq this.path.value '/') 'short'}}> <header class={{if (eq this.path.value '/') 'short'}}>
{{#if (gt @item.Definition.Match.HTTP.Methods.length 0) }} {{#if (gt @item.Definition.Match.HTTP.Methods.length 0) }}

View File

@ -1,7 +1,7 @@
<div <div
...attributes
> >
<a <a
...attributes
id={{@item.ID}} id={{@item.ID}}
class="splitter-card" class="splitter-card"
onclick={{optional @onclick}} onclick={{optional @onclick}}

View File

@ -1,16 +1,18 @@
import Helper from '@ember/component/helper'; import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
export default class DomPosition extends Helper { export default class DomPosition extends Helper {
@service('dom') dom; compute([target], { from }) {
if (typeof target === 'function') {
compute([target, from], hash) { return entry => {
const $target = this.dom.element(target); const $target = entry.target;
const $from = this.dom.element(from); let rect = $target.getBoundingClientRect();
const fromRect = $from.getBoundingClientRect(); if (typeof from !== 'undefined') {
const rect = $target.getBoundingClientRect(); const fromRect = from.getBoundingClientRect();
rect.x = rect.x - fromRect.x; rect.x = rect.x - fromRect.x;
rect.y = rect.y - fromRect.y; rect.y = rect.y - fromRect.y;
return rect; }
return target(rect);
};
}
} }
} }

View File

@ -120,6 +120,7 @@
"ember-modifier": "^2.1.1", "ember-modifier": "^2.1.1",
"ember-named-blocks-polyfill": "^0.2.3", "ember-named-blocks-polyfill": "^0.2.3",
"ember-on-helper": "^0.1.0", "ember-on-helper": "^0.1.0",
"ember-on-resize-modifier": "^0.3.0",
"ember-page-title": "^5.2.3", "ember-page-title": "^5.2.3",
"ember-power-select": "^4.0.5", "ember-power-select": "^4.0.5",
"ember-power-select-with-create": "^0.8.0", "ember-power-select-with-create": "^0.8.0",

View File

@ -8135,7 +8135,7 @@ ember-modifier-manager-polyfill@^1.1.0, ember-modifier-manager-polyfill@^1.2.0:
ember-cli-version-checker "^2.1.2" ember-cli-version-checker "^2.1.2"
ember-compatibility-helpers "^1.2.0" ember-compatibility-helpers "^1.2.0"
ember-modifier@^2.1.1: ember-modifier@^2.1.0, ember-modifier@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-2.1.1.tgz#aa3a12e2d6cf1622f774f3f1eab4880982a43fa9" resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-2.1.1.tgz#aa3a12e2d6cf1622f774f3f1eab4880982a43fa9"
integrity sha512-g9mcpFWgw5lgNU40YNf0USNWqoGTJ+EqjDQKjm7556gaRNDeGnLylFKqx9O3opwLHEt6ZODnRDy9U0S5YEMREg== integrity sha512-g9mcpFWgw5lgNU40YNf0USNWqoGTJ+EqjDQKjm7556gaRNDeGnLylFKqx9O3opwLHEt6ZODnRDy9U0S5YEMREg==
@ -8171,6 +8171,16 @@ ember-on-helper@^0.1.0:
dependencies: dependencies:
ember-cli-babel "^7.7.3" ember-cli-babel "^7.7.3"
ember-on-resize-modifier@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/ember-on-resize-modifier/-/ember-on-resize-modifier-0.3.0.tgz#6c8b0fda3cd77c9f51c5e8394ed7af13b2c20fcb"
integrity sha512-LUZcO3dYJXTcUFI2/8X+wyZXEp0p/xDZS3UHxwI/j99MprL4ZNxdYELQ5Rhq0sR/eGMBaJMEMzgM7I62+irOrg==
dependencies:
ember-cli-babel "^7.20.5"
ember-cli-htmlbars "^5.1.2"
ember-modifier "^2.1.0"
ember-resize-observer-service "^0.3.0"
ember-page-title@^5.2.3: ember-page-title@^5.2.3:
version "5.2.3" version "5.2.3"
resolved "https://registry.yarnpkg.com/ember-page-title/-/ember-page-title-5.2.3.tgz#63b039d70d4a5d7db9c00de5b2108823fb90bb9d" resolved "https://registry.yarnpkg.com/ember-page-title/-/ember-page-title-5.2.3.tgz#63b039d70d4a5d7db9c00de5b2108823fb90bb9d"
@ -8243,6 +8253,14 @@ ember-require-module@^0.3.0:
dependencies: dependencies:
ember-cli-babel "^6.9.2" ember-cli-babel "^6.9.2"
ember-resize-observer-service@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/ember-resize-observer-service/-/ember-resize-observer-service-0.3.0.tgz#69b6e29bd6d742001ebe0ec70249f20f46fedc4a"
integrity sha512-FrKPowJ9CwLBok+WZOhudoRXPg9xHArpotMdZ2FyztcBHIb8D1mVB6ELLw62KGa62Wf7RoRhfmkloZax/5WHwg==
dependencies:
ember-cli-babel "^7.20.5"
ember-cli-htmlbars "^5.1.2"
ember-resolver@^8.0.0: ember-resolver@^8.0.0:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-8.0.2.tgz#8a45a744aaf5391eb52b4cb393b3b06d2db1975c" resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-8.0.2.tgz#8a45a744aaf5391eb52b4cb393b3b06d2db1975c"