From db231518b999903a7eda129c9c3d6dc5a3c21a8f Mon Sep 17 00:00:00 2001 From: fraxken Date: Wed, 6 Dec 2023 12:02:51 +0100 Subject: [PATCH] refactor: improve lock/unlock behavior --- public/common/utils.js | 6 ++ public/components/locker/locker.js | 73 ++++++++++----- public/components/searchbar/searchbar.js | 24 ++++- .../views/home/maintainers/maintainers.js | 26 ++++-- workspaces/vis-network/src/network.js | 91 ++++++++----------- 5 files changed, 127 insertions(+), 93 deletions(-) diff --git a/public/common/utils.js b/public/common/utils.js index cca77131..3f247285 100644 --- a/public/common/utils.js +++ b/public/common/utils.js @@ -7,6 +7,12 @@ import { createExpandableSpan } from "../components/expandable/expandable"; window.activeLegendElement = null; +export function vec2Distance(location, pos) { + return Math.sqrt( + Math.pow(location.x - pos.x, 2) + Math.pow(location.y - pos.y, 2) + ); +} + export function getVCSRepositoryPathAndPlatform(url) { if (!url) { return null; diff --git a/public/components/locker/locker.js b/public/components/locker/locker.js index c496de83..144c8e2a 100644 --- a/public/components/locker/locker.js +++ b/public/components/locker/locker.js @@ -4,7 +4,9 @@ export class Locker { this.dom = document.getElementById("network-locker"); this.networkView = document.getElementById("network--view"); this.nsn = nsn; - this.unlock(); + this.locked = false; + this.unlockAuthorized = true; + this.renderUnlock(); document.addEventListener("keydown", (event) => { const hotkeys = JSON.parse(localStorage.getItem("hotkeys")); @@ -15,33 +17,62 @@ export class Locker { } } }); - this.dom.addEventListener("click", () => { - this.auto(); - }); + this.dom.addEventListener("click", () => this.auto()); + this.nsn.network.on("highlight_done", this.highlightDone.bind(this)); + } - this.nsn.network.on("highlight_done", () => { - this.unlock(); - }); + highlightDone() { + if (!this.unlockAuthorized) { + return; + } + + console.log("[LOCKER] highlight done emitted"); + this.unlockAuthorized = false; + setTimeout(() => { + this.unlockAuthorized = true; + }, 1); + + this.unlock(); } auto() { - const wasLocked = this.locked === true; + // Refuse locking if there is no multi selections + if (this.nsn.lastHighlightedIds === null) { + return; + } + this[this.locked ? "unlock" : "lock"](); + } - if (wasLocked) { - if (window.networkNav.currentNodeParams === null) { - this.nsn.resetHighlight(); - } - else { - this.nsn.neighbourHighlight(window.networkNav.currentNodeParams); - } + lock() { + if (!this.locked) { + console.log("[LOCKER] lock triggered"); + this.renderLock(); + this.locked = true; } } - lock(force = false) { - if (window.networkNav.currentNodeParams !== null && !force) { + unlock() { + if (!this.locked) { return; } + + console.log("[LOCKER] unlock triggered"); + this.renderUnlock(); + this.locked = false; + + // No node selected, so we reset highlight + const selectedNode = window.networkNav.currentNodeParams; + if (selectedNode === null) { + this.nsn.resetHighlight(); + } + else if (this.nsn.lastHighlightedIds !== null) { + this.nsn.lastHighlightedIds = null; + this.nsn.neighbourHighlight(selectedNode); + } + } + + renderLock() { this.dom.classList.add("enabled"); this.dom.querySelector("p").textContent = "LOCKED"; this.networkView.classList.add("locked"); @@ -50,12 +81,9 @@ export class Locker { iconElement.classList.remove("icon-lock-open"); iconElement.classList.add("icon-lock"); iconElement.classList.add("enabled"); - - this.nsn.lock(); - this.locked = true; } - unlock() { + renderUnlock() { this.dom.classList.remove("enabled"); this.dom.querySelector("p").textContent = "UNLOCKED"; this.networkView.classList.remove("locked"); @@ -64,8 +92,5 @@ export class Locker { iconElement.classList.remove("icon-lock"); iconElement.classList.remove("enabled"); iconElement.classList.add("icon-lock-open"); - - this.nsn.unlock(); - this.locked = false; } } diff --git a/public/components/searchbar/searchbar.js b/public/components/searchbar/searchbar.js index d412ea52..6e7e15eb 100644 --- a/public/components/searchbar/searchbar.js +++ b/public/components/searchbar/searchbar.js @@ -3,7 +3,7 @@ import semver from "semver"; import sizeSatisfies from "@nodesecure/size-satisfies"; // Import Internal Dependencies -import { createDOMElement } from "../../common/utils.js"; +import { createDOMElement, vec2Distance } from "../../common/utils.js"; // CONSTANTS const kFiltersName = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size"]); @@ -408,12 +408,28 @@ export class SearchBar { window.navigation.setNavByName("network--view"); this.delayOpenSearchBar = false; - if (window.locker.locked) { - this.network.resetHighlight(); - } this.network.highlightMultipleNodes(nodeIds); window.locker.lock(); + const currentSelectedNode = window.networkNav.currentNodeParams; + const moveTo = !currentSelectedNode || !nodeIds.includes(currentSelectedNode.nodes[0]); + if (moveTo) { + const origin = this.network.network.getViewPosition(); + const closestNode = nodeIds + .map((id) => { + return { id, pos: this.network.network.getPosition(id) }; + }) + .reduce( + (a, b) => (vec2Distance(origin, a.pos) < vec2Distance(origin, b.pos) ? a : b) + ); + + const scale = nodeIds.length > 3 ? 0.25 : 0.35; + this.network.network.focus(closestNode.id, { + animation: true, + scale + }); + } + this.close(); setTimeout(() => { diff --git a/public/components/views/home/maintainers/maintainers.js b/public/components/views/home/maintainers/maintainers.js index c72fb1a1..9410d15b 100644 --- a/public/components/views/home/maintainers/maintainers.js +++ b/public/components/views/home/maintainers/maintainers.js @@ -127,21 +127,27 @@ export class PopupMaintainer { globeElement.addEventListener("click", () => { const nodeIds = [...this.nsn.findNodeIds(new Set(packagesList))]; - this.nsn.resetHighlight(); this.nsn.highlightMultipleNodes(nodeIds); - if (!window.locker.locked) { - window.locker.lock(true); - } - + window.locker.lock(); window.popup.close(); window.navigation.setNavByName("network--view"); - const moveTo = window.networkNav.currentNodeParams === null || - !nodeIds.includes(window.networkNav.currentNodeParams.nodes[0]); + const currentSelectedNode = window.networkNav.currentNodeParams; + const moveTo = currentSelectedNode === null || !nodeIds.includes(currentSelectedNode.nodes[0]); if (moveTo) { - this.nsn.network.moveTo({ - position: this.nsn.network.getPosition(nodeIds[0]), - animation: true + const origin = this.nsn.network.getViewPosition(); + const closestNode = nodeIds + .map((id) => { + return { id, pos: this.nsn.network.getPosition(id) }; + }) + .reduce( + (a, b) => (utils.vec2Distance(origin, a.pos) < utils.vec2Distance(origin, b.pos) ? a : b) + ); + + const scale = nodeIds.length > 3 ? 0.25 : 0.35; + this.nsn.network.focus(closestNode.id, { + animation: true, + scale }); } }); diff --git a/workspaces/vis-network/src/network.js b/workspaces/vis-network/src/network.js index 18f86a08..ff853e38 100644 --- a/workspaces/vis-network/src/network.js +++ b/workspaces/vis-network/src/network.js @@ -70,7 +70,6 @@ export default class NodeSecureNetwork { this.highlightEnabled = false; this.isLoaded = false; - this.locked = false; this.lastHighlightedIds = null; const { nodes, edges } = secureDataSet.build(); @@ -107,24 +106,6 @@ 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} @@ -214,8 +195,8 @@ export default class NodeSecureNetwork { } highlightMultipleNodes(nodeIds) { - if (this.locked) { - return; + if (this.lastHighlightedIds !== null) { + this.resetHighlight(); } this.network.startSimulation(); @@ -266,6 +247,29 @@ export default class NodeSecureNetwork { this.network.stopSimulation(); } + resetHighlight() { + const allNodes = this.nodes.get(); + const allEdges = this.edges.get(); + + // reset all edge labels - even if user clicks on empty space + for (let id = 0; id < allEdges.length; id++) { + Object.assign(allEdges[id], CONSTANTS.LABELS.NONE); + } + + this.highlightEnabled = false; + for (const node of allNodes) { + const { id, hasWarnings } = this.linker.get(Number(node.id)); + + Object.assign(node, utils.getNodeColor(id, hasWarnings, this.theme)); + } + + this.lastHighlightedIds = null; + this.network.startSimulation(); + this.nodes.update(allNodes); + this.edges.update(allEdges); + this.network.stopSimulation(); + } + /** * Search for neighbours nodes of a given node * @@ -287,6 +291,10 @@ export default class NodeSecureNetwork { } lockedNeighbourHighlight(params) { + if (this.lastHighlightedIds === null) { + return false; + } + if (!params || params.nodes.length === 0) { return true; } @@ -296,7 +304,6 @@ export default class NodeSecureNetwork { return false; } - this.network.startSimulation(); const allNodes = this.nodes.get({ returnType: "Object" }); for (const node of Object.values(allNodes)) { if (!this.lastHighlightedIds.has(node.id)) { @@ -328,47 +335,25 @@ export default class NodeSecureNetwork { } + this.network.startSimulation(); this.nodes.update(Object.values(allNodes)); this.network.focus(selectedNode, { animation: true, scale: 0.35, - offset: { x: 250, y: 0 } + offset: { x: 150, y: 0 } }); this.network.stopSimulation(); return true; } - resetHighlight() { - this.network.startSimulation(); - - const allNodes = this.nodes.get(); - const allEdges = this.edges.get(); - - // reset all edge labels - even if user clicks on empty space - for (let id = 0; id < allEdges.length; id++) { - Object.assign(allEdges[id], CONSTANTS.LABELS.NONE); - } - - this.highlightEnabled = false; - for (const node of allNodes) { - const { id, hasWarnings } = this.linker.get(Number(node.id)); - - Object.assign(node, utils.getNodeColor(id, hasWarnings, this.theme)); - } - - this.locked = false; - this.lastHighlightedIds = null; - - this.nodes.update(allNodes); - this.edges.update(allEdges); - this.network.stopSimulation(); - } - neighbourHighlight(params) { - if (this.lastHighlightedIds !== null && this.locked && this.lockedNeighbourHighlight(params)) { + if (this.lockedNeighbourHighlight(params)) { + console.log("[NETWORK] locked, stop neighbour highlight"); + return; } + console.log("[NETWORK] neighbour highlight start"); const allNodes = this.nodes.get({ returnType: "Object" }); const allEdges = this.edges.get(); @@ -425,11 +410,10 @@ export default class NodeSecureNetwork { } } - // offset set to 250 to compensate for the package info slide in on the left of screen this.network.focus(selectedNode, { animation: true, scale: 0.35, - offset: { x: 250, y: 0 } + offset: { x: 150, y: 0 } }); } else if (this.highlightEnabled) { @@ -441,10 +425,7 @@ 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();