Skip to content

Commit 3da30d7

Browse files
authored
feat: lock/unlock network & highlight multipleNodes (#314)
1 parent 250ab75 commit 3da30d7

20 files changed

Lines changed: 424 additions & 25 deletions

File tree

i18n/english.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const ui = {
3434
title: "Shortcuts",
3535
blockquote: "Click on hotkey to update",
3636
goto: "Goto",
37-
openCloseWiki: "Open/Close wiki"
37+
openCloseWiki: "Open/Close wiki",
38+
lock: "Lock/Unlock network"
3839
}
3940
}
4041
};

i18n/french.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const ui = {
3434
title: "Raccourcis",
3535
blockquote: "Cliquer sur le raccourci clavier pour mettre à jour",
3636
goto: "Ouvrir",
37-
openCloseWiki: "Ouverture/Fermeture du wiki"
37+
openCloseWiki: "Ouverture/Fermeture du wiki",
38+
lock: "Verrouiller/Déverrouiller le réseau"
3839
}
3940
}
4041
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#network-locker {
2+
position: absolute;
3+
bottom: 10px;
4+
right: 10px;
5+
z-index: 30;
6+
display: flex;
7+
height: 30px;
8+
border-radius: 4px;
9+
align-items: center;
10+
box-sizing: border-box;
11+
overflow: hidden;
12+
background-color: var(--primary);
13+
transition: 0.3s all linear;
14+
cursor: pointer;
15+
}
16+
17+
#network-locker:not(.enabled) {
18+
background-color: var(--primary);
19+
}
20+
21+
#network-locker.enabled {
22+
background-color: #af2222;
23+
}
24+
25+
#network-locker>i {
26+
height: inherit;
27+
padding: 0 5px;
28+
display: flex;
29+
align-items: center;
30+
border-radius: 4px;
31+
margin-right: 10px;
32+
transition: 0.3s all linear;
33+
}
34+
35+
#network-locker>i:not(.enabled) {
36+
background-color: var(--primary-lighter);
37+
}
38+
39+
#network-locker>i.enabled {
40+
background-color: #cb3d3d;
41+
}
42+
43+
#network-locker>p {
44+
font-family: "mononoki";
45+
padding-right: 10px;
46+
display: flex;
47+
align-items: center;
48+
height: inherit;
49+
}

public/components/locker/locker.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
export class Locker {
3+
constructor(nsn) {
4+
this.dom = document.getElementById("network-locker");
5+
this.networkView = document.getElementById("network--view");
6+
this.nsn = nsn;
7+
this.unlock();
8+
9+
document.addEventListener("keydown", (event) => {
10+
const hotkeys = JSON.parse(localStorage.getItem("hotkeys"));
11+
switch (event.key.toUpperCase()) {
12+
case hotkeys.lock: {
13+
this.auto();
14+
break;
15+
}
16+
}
17+
});
18+
this.dom.addEventListener("click", () => {
19+
this.auto();
20+
});
21+
22+
this.nsn.network.on("highlight_done", () => {
23+
this.unlock();
24+
});
25+
}
26+
27+
auto() {
28+
const wasLocked = this.locked === true;
29+
this[this.locked ? "unlock" : "lock"]();
30+
31+
if (wasLocked) {
32+
if (window.networkNav.currentNodeParams === null) {
33+
this.nsn.resetHighlight();
34+
}
35+
else {
36+
this.nsn.neighbourHighlight(window.networkNav.currentNodeParams);
37+
}
38+
}
39+
}
40+
41+
lock(force = false) {
42+
if (window.networkNav.currentNodeParams !== null && !force) {
43+
return;
44+
}
45+
this.dom.classList.add("enabled");
46+
this.dom.querySelector("p").textContent = "LOCKED";
47+
this.networkView.classList.add("locked");
48+
49+
const iconElement = this.dom.querySelector("i");
50+
iconElement.classList.remove("icon-lock-open");
51+
iconElement.classList.add("icon-lock");
52+
iconElement.classList.add("enabled");
53+
54+
this.nsn.lock();
55+
this.locked = true;
56+
}
57+
58+
unlock() {
59+
this.dom.classList.remove("enabled");
60+
this.dom.querySelector("p").textContent = "UNLOCKED";
61+
this.networkView.classList.remove("locked");
62+
63+
const iconElement = this.dom.querySelector("i");
64+
iconElement.classList.remove("icon-lock");
65+
iconElement.classList.remove("enabled");
66+
iconElement.classList.add("icon-lock-open");
67+
68+
this.nsn.unlock();
69+
this.locked = false;
70+
}
71+
}

public/components/searchbar/searchbar.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ export class SearchBar {
104104
if (this.activeQuery.size === 0) {
105105
this.allSearchPackages.forEach((element) => element.classList.add("hide"));
106106
}
107-
108107
// if backspace is received and that we have active queries
109108
// then we want the query to be re-inserted in the input field!
110109
else if (event.key === "Backspace") {
@@ -116,6 +115,17 @@ export class SearchBar {
116115
confirmBackspace = true;
117116
}
118117
}
118+
else if (event.key === "Enter") {
119+
const nodeIds = [];
120+
121+
for (const pkgElement of this.allSearchPackages) {
122+
if (!pkgElement.classList.contains("hide")) {
123+
nodeIds.push(Number(pkgElement.getAttribute("data-value")));
124+
}
125+
}
126+
127+
this.focusMultipleNodeIds(nodeIds);
128+
}
119129

120130
return;
121131
}
@@ -164,6 +174,7 @@ export class SearchBar {
164174
this.input.value.slice(currentActiveQueryName.length + 1).trim();
165175

166176
this.showHelperByInputText(text);
177+
167178
if (text.length === 0) {
168179
return;
169180
}
@@ -186,6 +197,7 @@ export class SearchBar {
186197
return;
187198
}
188199
}
200+
189201
this.showResultsByIds(matchingIds);
190202
});
191203

@@ -201,7 +213,7 @@ export class SearchBar {
201213
const self = this;
202214
for (const domElement of this.allSearchPackages) {
203215
domElement.addEventListener("click", function clikEvent() {
204-
self.resultRowClick(this.getAttribute("data-value"));
216+
self.focusNodeById(this.getAttribute("data-value"));
205217
});
206218
}
207219
}
@@ -381,10 +393,27 @@ export class SearchBar {
381393
return storedIds.size === 0 ? matchingIds : new Set([...matchingIds].filter((value) => storedIds.has(value)));
382394
}
383395

384-
resultRowClick(dataValue) {
396+
focusNodeById(nodeId) {
385397
window.navigation.setNavByName("network--view");
386398
this.delayOpenSearchBar = false;
387-
this.network.focusNodeById(dataValue);
399+
this.network.focusNodeById(nodeId);
400+
this.close();
401+
402+
setTimeout(() => {
403+
this.delayOpenSearchBar = true;
404+
}, 5);
405+
}
406+
407+
focusMultipleNodeIds(nodeIds) {
408+
window.navigation.setNavByName("network--view");
409+
this.delayOpenSearchBar = false;
410+
411+
if (window.locker.locked) {
412+
this.network.resetHighlight();
413+
}
414+
this.network.highlightMultipleNodes(nodeIds);
415+
window.locker.lock();
416+
388417
this.close();
389418

390419
setTimeout(() => {

public/components/views/home/maintainers/maintainers.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class Maintainers {
6565
person.classList.add("hidden");
6666
}
6767
person.addEventListener("click", () => {
68+
// TODO: close package info?
6869
window.popup.open(
6970
new PopupMaintainer(name, data, this.nsn).render()
7071
);
@@ -115,10 +116,34 @@ export class PopupMaintainer {
115116
linkElement.style.display = "none";
116117
}
117118

118-
const focusGlobElement = clone.querySelector(".icon-globe-alt-outline");
119-
focusGlobElement.addEventListener("click", () => {
119+
const globeElement = clone.querySelector(".icon-globe-alt-outline");
120+
const packagesList = [...this.data.packages]
121+
.map((spec) => {
122+
const { name, version } = utils.parseNpmSpec(spec);
123+
124+
return `${name}@${version}`;
125+
});
126+
127+
globeElement.addEventListener("click", () => {
128+
const nodeIds = [...this.nsn.findNodeIds(new Set(packagesList))];
129+
130+
this.nsn.resetHighlight();
131+
this.nsn.highlightMultipleNodes(nodeIds);
132+
if (!window.locker.locked) {
133+
window.locker.lock(true);
134+
}
135+
120136
window.popup.close();
121137
window.navigation.setNavByName("network--view");
138+
139+
const moveTo = window.networkNav.currentNodeParams === null ||
140+
!nodeIds.includes(window.networkNav.currentNodeParams.nodes[0]);
141+
if (moveTo) {
142+
this.nsn.network.moveTo({
143+
position: this.nsn.network.getPosition(nodeIds[0]),
144+
animation: true
145+
});
146+
}
122147
});
123148

124149
this.generatePackagesList(clone);

public/components/views/network/network.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
#network--view {
22
z-index: 10;
3+
transition: 0.3s all linear;
4+
}
5+
6+
#network--view:not(.locked) {
7+
box-shadow: -2px -2px 10px #6d29b5b8 inset;
8+
}
9+
10+
#network--view.locked {
11+
box-shadow: -2px -2px 10px #b52929b8 inset;
312
}
413

514
#network-loader {

public/components/views/settings/settings.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const kDefaultHotKeys = {
1111
home: "H",
1212
network: "N",
1313
settings: "S",
14-
wiki: "W"
14+
wiki: "W",
15+
lock: "L"
1516
};
1617

1718
export class Settings {

public/font/fontello/config.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,18 @@
329329
"css": "globe-alt",
330330
"code": 59430,
331331
"src": "typicons"
332+
},
333+
{
334+
"uid": "c1f1975c885aa9f3dad7810c53b82074",
335+
"css": "lock",
336+
"code": 59431,
337+
"src": "fontawesome"
338+
},
339+
{
340+
"uid": "05376be04a27d5a46e855a233d6e8508",
341+
"css": "lock-open",
342+
"code": 61758,
343+
"src": "fontawesome"
332344
}
333345
]
334-
}
346+
}

public/font/fontello/fontello.css

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
@font-face {
22
font-family: 'fontello';
3-
src: url('./fontello.eot?8115607');
4-
src: url('./fontello.eot?8115607#iefix') format('embedded-opentype'),
5-
url('./fontello.woff2?8115607') format('woff2'),
6-
url('./fontello.woff?8115607') format('woff'),
7-
url('./fontello.ttf?8115607') format('truetype'),
8-
url('./fontello.svg?8115607#fontello') format('svg');
3+
src: url('./fontello.eot?84631897');
4+
src: url('./fontello.eot?84631897#iefix') format('embedded-opentype'),
5+
url('./fontello.woff2?84631897') format('woff2'),
6+
url('./fontello.woff?84631897') format('woff'),
7+
url('./fontello.ttf?84631897') format('truetype'),
8+
url('./fontello.svg?84631897#fontello') format('svg');
99
font-weight: normal;
1010
font-style: normal;
1111
}
@@ -15,7 +15,7 @@
1515
@media screen and (-webkit-min-device-pixel-ratio:0) {
1616
@font-face {
1717
font-family: 'fontello';
18-
src: url('../../font/fontello.svg?8115607#fontello') format('svg');
18+
src: url('../font/fontello.svg?84631897#fontello') format('svg');
1919
}
2020
}
2121
*/
@@ -92,6 +92,7 @@
9292
.icon-code:before { content: '\e824'; } /* '' */
9393
.icon-globe-alt-outline:before { content: '\e825'; } /* '' */
9494
.icon-globe-alt:before { content: '\e826'; } /* '' */
95+
.icon-lock:before { content: '\e827'; } /* '' */
9596
.icon-info-circled:before { content: '\f085'; } /* '' */
9697
.icon-info-circled-alt:before { content: '\f086'; } /* '' */
9798
.icon-docs:before { content: '\f0c5'; } /* '' */
@@ -100,6 +101,7 @@
100101
.icon-terminal:before { content: '\f120'; } /* '' */
101102
.icon-fork:before { content: '\f126'; } /* '' */
102103
.icon-unlink:before { content: '\f127'; } /* '' */
104+
.icon-lock-open:before { content: '\f13e'; } /* '' */
103105
.icon-minus-squared:before { content: '\f146'; } /* '' */
104106
.icon-minus-squared-alt:before { content: '\f147'; } /* '' */
105107
.icon-doc-inv:before { content: '\f15b'; } /* '' */

0 commit comments

Comments
 (0)