diff --git a/i18n/english.js b/i18n/english.js index c6d832d..f35e3a7 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -192,6 +192,11 @@ const ui = { "Available licenses": "Available licenses", "Available flags": "Available flags", default: "Search options" + }, + legend: { + default: "The package is fine.", + warn: "The package has warnings.", + friendly: "The package is maintained by the same authors as the root package." } }; diff --git a/i18n/french.js b/i18n/french.js index af27ac2..1a0aa5f 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -192,6 +192,11 @@ const ui = { "Available licenses": "Licences disponibles", "Available flags": "Drapeaux disponibles", default: "Options de recherche" + }, + legend: { + default: "Rien à signaler.", + warn: "La dépendance contient des menaces.", + friendly: "La dépendance est maintenu par des auteurs du package principal." } }; diff --git a/public/components/legend/legend.css b/public/components/legend/legend.css new file mode 100644 index 0000000..d6b932d --- /dev/null +++ b/public/components/legend/legend.css @@ -0,0 +1,60 @@ +#legend { + position: absolute; + right: 10px; + bottom: 40px; + z-index: 30; + max-width: 692px; + padding: 4px; + border-radius: 0 0 0 1rem; + overflow: hidden; + transition: transform 0.3s; + display: none; + flex-direction: column; + justify-content: right; + flex-wrap: wrap; + font-size: 14px; + color: #030421; + padding: 0 10px 10px 0; + border-radius: 4px; +} + +.legend-box { + box-sizing: border-box; + display: inline-flex; + flex-direction: row-reverse; + align-items: center; + height: 24px; + border-radius: 4px; +} + +.legend-badge { + display: inline-block; + width: 15px; + height: 15px; + margin: 0 5px; + border-radius: 50%; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.34); +} + +.legend { + font-weight: bold; + padding-left: 6px; + display: none; +} + +.legend-box:not(:hover) { + background: transparent !important; +} + +.legend-box:hover { + border: 1px solid rgba(48, 56, 165, 0.6); +} + +.legend-box:hover > .legend { + display: flex; + align-items: center; +} + +.legend-box:hover .legend-badge { + box-shadow: none; +} diff --git a/public/components/legend/legend.js b/public/components/legend/legend.js new file mode 100644 index 0000000..9ae47e8 --- /dev/null +++ b/public/components/legend/legend.js @@ -0,0 +1,74 @@ +// Import Internal Dependencies +import { COLORS } from "../../../workspaces/vis-network/src/constants.js"; +import { currentLang } from "../../common/utils.js"; + +export class Legend { + constructor(options = {}) { + const { show = false } = options; + const lang = currentLang(); + const theme = "LIGHT"; + const legend = document.getElementById("legend"); + const colors = COLORS[theme]; + + const defaultBadgeElement = document.createElement("div"); + defaultBadgeElement.classList.add("legend-badge"); + defaultBadgeElement.style.backgroundColor = colors.DEFAULT.color; + defaultBadgeElement.style.color = colors.DEFAULT.font.color; + const defaultElement = document.createElement("div"); + defaultElement.classList.add("legend"); + defaultElement.textContent = window.i18n[lang].legend.default; + + const warnBadgeElement = document.createElement("div"); + warnBadgeElement.classList.add("legend-badge"); + warnBadgeElement.style.backgroundColor = colors.WARN.color; + warnBadgeElement.style.color = colors.WARN.font.color; + const warnElement = document.createElement("div"); + warnElement.classList.add("legend"); + warnElement.textContent = window.i18n[lang].legend.warn; + + const friendlyBadgeElement = document.createElement("div"); + friendlyBadgeElement.classList.add("legend-badge"); + friendlyBadgeElement.style.backgroundColor = colors.FRIENDLY.color; + friendlyBadgeElement.style.color = colors.FRIENDLY.font.color; + const friendlyElement = document.createElement("div"); + friendlyElement.classList.add("legend"); + friendlyElement.textContent = window.i18n[lang].legend.friendly; + + const warnBox = document.createElement("div"); + warnBox.classList.add("legend-box"); + warnBox.appendChild(warnBadgeElement); + warnBox.appendChild(warnElement); + warnBox.style.backgroundColor = colors.WARN.color; + warnBox.style.color = colors.WARN.font.color; + + const friendlyBox = document.createElement("div"); + friendlyBox.classList.add("legend-box"); + friendlyBox.appendChild(friendlyBadgeElement); + friendlyBox.appendChild(friendlyElement); + friendlyBox.style.backgroundColor = colors.FRIENDLY.color; + friendlyBox.style.color = colors.FRIENDLY.font.color; + + const defaultBox = document.createElement("div"); + defaultBox.classList.add("legend-box"); + defaultBox.appendChild(defaultBadgeElement); + defaultBox.appendChild(defaultElement); + defaultBox.style.backgroundColor = colors.DEFAULT.color; + defaultBox.style.color = colors.DEFAULT.font.color; + + legend.appendChild(warnBox); + legend.appendChild(friendlyBox); + legend.appendChild(defaultBox); + + if (show) { + this.show(); + } + } + + show() { + document.getElementById("legend").style.display = "flex"; + } + + hide() { + document.getElementById("legend").style.display = "none"; + } +} diff --git a/public/components/views/settings/settings.css b/public/components/views/settings/settings.css index 9c3ed48..9585cc5 100644 --- a/public/components/views/settings/settings.css +++ b/public/components/views/settings/settings.css @@ -123,6 +123,10 @@ input[type="checkbox"] { cursor: pointer; } +label { + color: #4a5e68; +} + button.save { height: 30px; width: 100px; diff --git a/public/components/views/settings/settings.js b/public/components/views/settings/settings.js index 3ac2d79..9a9fe49 100644 --- a/public/components/views/settings/settings.js +++ b/public/components/views/settings/settings.js @@ -29,7 +29,9 @@ export class Settings { /** @type {HTMLInputElement[]} */ flagsCheckbox: document.querySelectorAll("input[name='flags']"), /** @type {HTMLInputElement} */ - shortcutsSection: document.querySelector(".shortcuts") + shortcutsSection: document.querySelector(".shortcuts"), + /** @type {HTMLInputElement} */ + showFriendlyDependenciesCheckbox: document.querySelector("#show-friendly") }; this.saveButton = document.querySelector(".save"); @@ -37,7 +39,7 @@ export class Settings { this.saveButton.classList.add("disabled"); this.dom.defaultPackageMenu.addEventListener("change", () => this.enableSaveButton()); - for (const checkbox of [...this.dom.warningsCheckbox, ...this.dom.flagsCheckbox]) { + for (const checkbox of [...this.dom.warningsCheckbox, ...this.dom.flagsCheckbox, this.dom.showFriendlyDependenciesCheckbox]) { checkbox.addEventListener("change", () => this.enableSaveButton()); } @@ -154,7 +156,8 @@ export class Settings { const newConfig = { defaultPackageMenu: this.dom.defaultPackageMenu.value || Settings.defaultMenuName, - ignore: { flags: new Set(), warnings: new Set() } + ignore: { flags: new Set(), warnings: new Set() }, + showFriendlyDependencies: this.dom.showFriendlyDependenciesCheckbox.checked }; for (const checkbox of this.dom.warningsCheckbox) { diff --git a/public/main.css b/public/main.css index 0f81335..7e90e50 100644 --- a/public/main.css +++ b/public/main.css @@ -6,6 +6,7 @@ @import url("./font/mononoki/mononoki.css"); @import url("./components/locker/locker.css"); +@import url("./components/legend/legend.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 7afe26e..1e65c0c 100644 --- a/public/main.js +++ b/public/main.js @@ -8,6 +8,7 @@ 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 { Legend } from "./components/legend/legend.js"; // Import Views Components import { Settings } from "./components/views/settings/settings.js"; @@ -40,6 +41,7 @@ document.addEventListener("DOMContentLoaded", async() => { NodeSecureNetwork.networkElementId = "dependency-graph"; const nsn = new NodeSecureNetwork(secureDataSet, { i18n: window.i18n[utils.currentLang()] }); window.locker = new Locker(nsn); + const legend = new Legend({ show: window.settings.config.showFriendlyDependencies }); new HomeView(secureDataSet, nsn); window.addEventListener("package-info-closed", () => { @@ -90,6 +92,13 @@ document.addEventListener("DOMContentLoaded", async() => { nsn.neighbourHighlight(networkNavigation.currentNodeParams, window.i18n[utils.currentLang()]); updateShowInfoMenu(networkNavigation.currentNodeParams); } + + if (event.detail.showFriendlyDependencies) { + legend.show(); + } + else { + legend.hide(); + } }); // Initialize searchbar diff --git a/views/index.html b/views/index.html index d32b061..25eb6a7 100644 --- a/views/index.html +++ b/views/index.html @@ -157,6 +157,8 @@

[[=z.token('network.unlocked')]]

+
+
+ +
+ +
+ + +
diff --git a/workspaces/vis-network/src/constants.js b/workspaces/vis-network/src/constants.js index 3b3af12..f5fc3ad 100644 --- a/workspaces/vis-network/src/constants.js +++ b/workspaces/vis-network/src/constants.js @@ -1,6 +1,3 @@ -// Import Internal Dependencies -import * as utils from "./utils.js"; - /** * SELECTED -> The color when a Node is selected. * CONNECTED_IN -> The color for first-degree nodes connected in the selected one. @@ -42,6 +39,12 @@ export const COLORS = Object.freeze({ color: "#FFF" } }, + FRIENDLY: { + color: "#e3fde3", + font: { + color: "#0e4522" + } + }, CONNECTED_IN: { color: "#C8E6C9", font: { @@ -68,6 +71,18 @@ export const COLORS = Object.freeze({ color: "#FFF" } }, + SELECTED_GROUP: { + color: "#1A237E", + font: { + color: "#FFF" + } + }, + SELECTED_LOCK: { + color: "#0D47A1", + font: { + color: "#FFF" + } + }, DEFAULT: { color: "rgba(150, 200, 200, 0.15)", font: { diff --git a/workspaces/vis-network/src/dataset.js b/workspaces/vis-network/src/dataset.js index 04307da..335872f 100644 --- a/workspaces/vis-network/src/dataset.js +++ b/workspaces/vis-network/src/dataset.js @@ -71,6 +71,12 @@ export default class NodeSecureDataSet extends EventTarget { this.rawEdgesData = []; this.rawNodesData = []; + const rootDependency = dataEntries.find(([name]) => name === data.rootDependencyName); + const rootContributors = [ + rootDependency[1].metadata.author, + ...rootDependency[1].metadata.maintainers, + ...rootDependency[1].metadata.publishers + ]; for (const [packageName, descriptor] of dataEntries) { const contributors = [descriptor.metadata.author, ...descriptor.metadata.maintainers, ...descriptor.metadata.publishers]; for (const [currVersion, opt] of Object.entries(descriptor.versions)) { @@ -98,17 +104,35 @@ export default class NodeSecureDataSet extends EventTarget { flags, hasWarnings ? this.flagsToIgnore : new Set([...this.flagsToIgnore, "hasWarnings"]) ); + const isFriendly = window.settings.config.showFriendlyDependencies & rootContributors.some( + (rootContributor) => contributors.some((contributor) => { + if (contributor.email && contributor.email === rootContributor.email) { + return true; + } + else if (contributor.name && contributor.name === rootContributor.name) { + return true; + } + + return false; + }) + ); + opt.isFriendly = isFriendly; this.packages.push({ id, name: packageName, version: currVersion, hasWarnings, flags: flagStr.replace(/\s/g, ""), - links + links, + isFriendly }); const label = `${packageName}@${currVersion}${flagStr}\n[${prettyBytes(size)}]`; - const color = utils.getNodeColor(id, hasWarnings); + const color = utils.getNodeColor({ + id, + hasWarnings, + isFriendly + }); color.font.multi = "html"; this.linker.set(Number(id), opt); diff --git a/workspaces/vis-network/src/network.js b/workspaces/vis-network/src/network.js index 9154767..b97cbca 100644 --- a/workspaces/vis-network/src/network.js +++ b/workspaces/vis-network/src/network.js @@ -266,9 +266,9 @@ export default class NodeSecureNetwork { this.highlightEnabled = false; for (const node of allNodes) { - const { id, hasWarnings } = this.linker.get(Number(node.id)); + const { id, hasWarnings, isFriendly } = this.linker.get(Number(node.id)); - Object.assign(node, utils.getNodeColor(id, hasWarnings, this.theme)); + Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly })); } this.lastHighlightedIds = null; @@ -427,9 +427,9 @@ export default class NodeSecureNetwork { else if (this.highlightEnabled) { this.highlightEnabled = false; for (const node of Object.values(allNodes)) { - const { id, hasWarnings } = this.linker.get(Number(node.id)); + const { id, hasWarnings, isFriendly } = this.linker.get(Number(node.id)); - Object.assign(node, utils.getNodeColor(id, hasWarnings, this.theme)); + Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly })); } } diff --git a/workspaces/vis-network/src/utils.js b/workspaces/vis-network/src/utils.js index f42ca95..be86292 100644 --- a/workspaces/vis-network/src/utils.js +++ b/workspaces/vis-network/src/utils.js @@ -30,12 +30,20 @@ export async function getJSON(path, customHeaders = Object.create(null)) { } /** - * @param {!number} id - * @param {boolean} [hasWarnings=false] - * @param {string} [theme=LIGHT] theme - * @returns {{color: string, font: {color: string }}} + * @params {object} options + * @param {string} options.id + * @param {string} options.hasWarnings + * @param {string} options.theme + * @param {string} options.isFriendly */ -export function getNodeColor(id, hasWarnings = false, theme = "LIGHT") { +export function getNodeColor(options) { + const { + id, + hasWarnings = false, + theme = "LIGHT", + isFriendly = false + } = options; + // id 0 is the root package (so by default he is highlighted as selected). if (id === 0) { return CONSTANTS.COLORS[theme].SELECTED; @@ -43,6 +51,9 @@ export function getNodeColor(id, hasWarnings = false, theme = "LIGHT") { else if (hasWarnings) { return CONSTANTS.COLORS[theme].WARN; } + else if (isFriendly) { + return CONSTANTS.COLORS[theme].FRIENDLY; + } return CONSTANTS.COLORS[theme].DEFAULT; } diff --git a/workspaces/vis-network/test/utils.test.js b/workspaces/vis-network/test/utils.test.js index 742e2c5..d99cebb 100644 --- a/workspaces/vis-network/test/utils.test.js +++ b/workspaces/vis-network/test/utils.test.js @@ -41,25 +41,25 @@ assert.equal( ); assert.equal( - getNodeColor(0), + getNodeColor({ id: 0 }), CONSTANTS.COLORS.LIGHT.SELECTED, "id 0 is the root package (so by default he is highlighted as selected)." ); assert.equal( - getNodeColor(1, true), + getNodeColor({ id: 1, hasWarnings: true }), CONSTANTS.COLORS.LIGHT.WARN, "hasWarnings is true, so the node is highlighted as warning." ); assert.equal( - getNodeColor(1, false), + getNodeColor({ id: 1, hasWarnings: false }), CONSTANTS.COLORS.LIGHT.DEFAULT, "the node is highlighted as default." ); assert.equal( - getNodeColor(1, false, "DARK"), + getNodeColor({ id: 1, hasWarnings: false, theme: "DARK" }), CONSTANTS.COLORS.DARK.DEFAULT, "the node is highlighted as default and the theme is DARK." );