diff --git a/i18n/english.js b/i18n/english.js index e9a84124..9380a79f 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -34,7 +34,8 @@ const ui = { title: "Shortcuts", blockquote: "Click on hotkey to update", goto: "Goto", - openCloseWiki: "Open/Close wiki" + openCloseWiki: "Open/Close wiki", + lock: "Lock/Unlock network" } } }; diff --git a/i18n/french.js b/i18n/french.js index 7b09de9c..d4a9c28a 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -34,7 +34,8 @@ const ui = { title: "Raccourcis", blockquote: "Cliquer sur le raccourci clavier pour mettre à jour", goto: "Ouvrir", - openCloseWiki: "Ouverture/Fermeture du wiki" + openCloseWiki: "Ouverture/Fermeture du wiki", + lock: "Verrouiller/Déverrouiller le réseau" } } }; diff --git a/public/components/locker/locker.css b/public/components/locker/locker.css new file mode 100644 index 00000000..c6c7059b --- /dev/null +++ b/public/components/locker/locker.css @@ -0,0 +1,49 @@ +#network-locker { + position: absolute; + bottom: 10px; + right: 10px; + z-index: 30; + display: flex; + height: 30px; + border-radius: 4px; + align-items: center; + box-sizing: border-box; + overflow: hidden; + background-color: var(--primary); + transition: 0.3s all linear; + cursor: pointer; +} + +#network-locker:not(.enabled) { + background-color: var(--primary); +} + +#network-locker.enabled { + background-color: #af2222; +} + +#network-locker>i { + height: inherit; + padding: 0 5px; + display: flex; + align-items: center; + border-radius: 4px; + margin-right: 10px; + transition: 0.3s all linear; +} + +#network-locker>i:not(.enabled) { + background-color: var(--primary-lighter); +} + +#network-locker>i.enabled { + background-color: #cb3d3d; +} + +#network-locker>p { + font-family: "mononoki"; + padding-right: 10px; + display: flex; + align-items: center; + height: inherit; +} diff --git a/public/components/locker/locker.js b/public/components/locker/locker.js new file mode 100644 index 00000000..979d836f --- /dev/null +++ b/public/components/locker/locker.js @@ -0,0 +1,54 @@ + +export class Locker { + constructor(nsn) { + this.dom = document.getElementById("network-locker"); + this.networkView = document.getElementById("network--view"); + this.nsn = nsn; + this.unlock(); + + document.addEventListener("keydown", (event) => { + const hotkeys = JSON.parse(localStorage.getItem("hotkeys")); + switch (event.key.toUpperCase()) { + case hotkeys.lock: { + this.auto(); + break; + } + } + }); + this.dom.addEventListener("click", () => { + this.auto(); + }); + } + + auto() { + this[this.locked ? "unlock" : "lock"](); + } + + lock() { + this.dom.classList.add("enabled"); + this.dom.querySelector("p").textContent = "LOCKED"; + this.networkView.classList.add("locked"); + + const iconElement = this.dom.querySelector("i"); + iconElement.classList.remove("icon-plus-squared"); + iconElement.classList.add("icon-minus-squared"); + iconElement.classList.add("enabled"); + + this.nsn.lock(); + this.locked = true; + } + + unlock() { + this.dom.classList.remove("enabled"); + this.dom.querySelector("p").textContent = "UNLOCKED"; + this.networkView.classList.remove("locked"); + + const iconElement = this.dom.querySelector("i"); + iconElement.classList.remove("icon-minus-squared"); + iconElement.classList.remove("enabled"); + iconElement.classList.add("icon-plus-squared"); + + this.nsn.unlock(); + this.locked = false; + } +} diff --git a/public/components/views/network/network.css b/public/components/views/network/network.css index b6f234e8..47301ff1 100644 --- a/public/components/views/network/network.css +++ b/public/components/views/network/network.css @@ -1,5 +1,14 @@ #network--view { z-index: 10; + transition: 0.3s all linear; +} + +#network--view:not(.locked) { + box-shadow: -2px -2px 10px #6d29b5b8 inset; +} + +#network--view.locked { + box-shadow: -2px -2px 10px #b52929b8 inset; } #network-loader { diff --git a/public/components/views/settings/settings.js b/public/components/views/settings/settings.js index 2d1653d6..4a891e8a 100644 --- a/public/components/views/settings/settings.js +++ b/public/components/views/settings/settings.js @@ -11,7 +11,8 @@ const kDefaultHotKeys = { home: "H", network: "N", settings: "S", - wiki: "W" + wiki: "W", + lock: "L" }; export class Settings { diff --git a/public/main.css b/public/main.css index f0101666..02e8ac83 100644 --- a/public/main.css +++ b/public/main.css @@ -5,6 +5,7 @@ @import url("./font/roboto/roboto.css"); @import url("./font/mononoki/mononoki.css"); +@import url("./components/locker/locker.css"); @import url("./components/popup/popup.css"); @import url("./components/file-box/file-box.css"); @import url("./components/expandable/expandable.css"); diff --git a/public/main.js b/public/main.js index 67fc7cc2..d73047c1 100644 --- a/public/main.js +++ b/public/main.js @@ -7,6 +7,7 @@ import { ViewNavigation } from "./components/navigation/navigation.js"; import { Wiki } from "./components/wiki/wiki.js"; import { SearchBar } from "./components/searchbar/searchbar.js"; import { Popup } from "./components/popup/popup.js"; +import { Locker } from "./components/locker/locker.js"; // Import Views Components import { Settings } from "./components/views/settings/settings.js"; @@ -14,6 +15,7 @@ import { HomeView } from "./components/views/home/home.js"; import { NetworkNavigation } from "./core/network-navigation.js"; document.addEventListener("DOMContentLoaded", async() => { + window.locker = null; window.popup = new Popup(); window.settings = await new Settings().fetchUserConfig(); window.navigation = new ViewNavigation(); @@ -30,8 +32,28 @@ document.addEventListener("DOMContentLoaded", async() => { // Initialize vis Network NodeSecureNetwork.networkElementId = "dependency-graph"; const nsn = new NodeSecureNetwork(secureDataSet); + window.locker = new Locker(nsn); new HomeView(secureDataSet, nsn); + // document.addEventListener("keydown", (event) => { + // if (event.code === "KeyP") { + // nsn.highlightMultipleNodes( + // nsn.findNodeIds( + // new Set([ + // "ansi-styles@4.3.0", + // "warp-ansi", + // "default-browser@4.0.0", + // "@nodesecure/licenses-conformance" + // ]) + // ) + // ); + // } + // else if (event.code === "KeyL") { + // nsn.lock(); + // console.log("locked: ", nsn.locked); + // } + // }); + window.addEventListener("package-info-closed", () => { networkNavigation.currentNodeParams = null; packageInfoOpened = false; diff --git a/views/index.html b/views/index.html index 380ffc49..f48f471a 100644 --- a/views/index.html +++ b/views/index.html @@ -147,8 +147,10 @@

[[=z.token('loading_nodes')]]

[[=z.token('please_wait')]]

-
- +
+
+ +

UNLOCKED

+
+ + +
@@ -311,7 +317,7 @@

[[=z.token('settings.shortcuts.title')]]

- +
diff --git a/workspaces/vis-network/src/constants.js b/workspaces/vis-network/src/constants.js index 3e8543fe..0277c038 100644 --- a/workspaces/vis-network/src/constants.js +++ b/workspaces/vis-network/src/constants.js @@ -15,6 +15,12 @@ export const COLORS = Object.freeze({ color: "#FFF" } }, + SELECTED_LOCK: { + color: "#6200EA", + font: { + color: "#FFF" + } + }, DEFAULT: { color: "#E3F2FD", font: { @@ -100,9 +106,10 @@ export const LABELS = Object.freeze({ } }, NONE: { - label: " ", // A space is used to simulate resetting the edge laebl + // A space is used to simulate resetting the edge laebl + label: " ", font: { - background: "Transparent", + background: "Transparent" } } -}) +}); diff --git a/workspaces/vis-network/src/network.js b/workspaces/vis-network/src/network.js index 877a5268..d8049a6d 100644 --- a/workspaces/vis-network/src/network.js +++ b/workspaces/vis-network/src/network.js @@ -69,6 +69,9 @@ export default class NodeSecureNetwork { this.secureDataSet = secureDataSet; this.highlightEnabled = false; this.isLoaded = false; + + this.locked = false; + this.lastHighlightedIds = new Set(); const { nodes, edges } = secureDataSet.build(); const theme = options.theme?.toUpperCase() ?? "LIGHT"; @@ -104,6 +107,38 @@ export default class NodeSecureNetwork { this.network.stabilize(500); } + lock() { + if (this.locked) { + return; + } + + this.locked = true; + this.network.emit("lock", this.locked); + } + + unlock() { + if (!this.locked) { + return; + } + + this.locked = false; + this.network.emit("lock", this.locked); + } + + /** + * @param {!Set} packages + * @returns {IterableIterator} + */ + * findNodeIds(packages) { + for (const [id, opt] of this.linker) { + const spec = `${opt.name}@${opt.version}`; + + if (packages.has(opt.name) || packages.has(spec)) { + yield id; + } + } + } + /** * @description Focus/move to a Node by id * @param {number} [id=0] @@ -178,6 +213,41 @@ export default class NodeSecureNetwork { this.nodes.update(updatedNodes); } + highlightMultipleNodes(nodeIds) { + if (this.locked) { + return; + } + this.network.startSimulation(); + + const allNodes = this.nodes.get(); + const allEdges = this.edges.get(); + + // mark all nodes as hard to read. + const nodeIdsToHighlight = new Set(nodeIds); + for (const node of allNodes) { + const color = nodeIdsToHighlight.has(node.id) ? + this.colors.SELECTED : + this.colors.HARDTOREAD; + + Object.assign(node, color); + } + + // reset all edge labels - even if user clicks on empty space + for (let id = 0; id < allEdges.length; id++) { + Object.assign(allEdges[id], { + label: " ", + font: { + background: "Transparent" + } + }); + } + + this.lastHighlightedIds = nodeIdsToHighlight; + this.nodes.update(allNodes); + this.edges.update(allEdges); + this.network.stopSimulation(); + } + /** * Search for neighbours nodes of a given node * @@ -198,7 +268,41 @@ export default class NodeSecureNetwork { } } + lockedNeighbourHighlight(params) { + if (params.nodes.length === 0) { + return true; + } + + const selectedNode = params.nodes[0]; + if (!this.lastHighlightedIds.has(selectedNode)) { + return false; + } + + const allNodes = this.nodes.get(); + for (const node of allNodes) { + if (!this.lastHighlightedIds.has(node.id)) { + continue; + } + + const color = node.id === selectedNode ? + this.colors.SELECTED_LOCK : + this.colors.SELECTED; + + Object.assign(node, color); + } + + this.network.startSimulation(); + this.nodes.update(allNodes); + this.network.stopSimulation(); + + return true; + } + neighbourHighlight(params) { + if (this.locked && this.lockedNeighbourHighlight(params)) { + return; + } + const allNodes = this.nodes.get({ returnType: "Object" }); const allEdges = this.edges.get(); @@ -267,6 +371,8 @@ export default class NodeSecureNetwork { } // transform the object into an array + this.lastHighlightedIds = null; + this.locked = false; this.nodes.update(Object.values(allNodes)); this.edges.update(allEdges); this.network.stopSimulation();