Skip to content

Commit

Permalink
refactor: split package component into multiple class
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken committed Nov 24, 2023
1 parent 077cbb2 commit 266dd87
Show file tree
Hide file tree
Showing 14 changed files with 1,107 additions and 807 deletions.
806 changes: 0 additions & 806 deletions public/js/components/package.info.js

This file was deleted.

File renamed without changes.
220 changes: 220 additions & 0 deletions public/js/components/package/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Import Third-party Dependencies
import { getFlagsEmojisInlined } from "@nodesecure/vis-network";

// Import Internal Dependencies
import * as utils from "../../utils.js";

export class PackageHeader {
static ExternalLinks = {
socket: "https://socket.dev/npm/package/",
snykAdvisor: "https://snyk.io/advisor/npm-package/"
};

constructor(pkg) {
this.package = pkg;
this.nsn = this.package.nsn;
}

/**
* @param {!HTMLTemplateElement} clone
*/
generate(clone) {
const {
name: packageName,
version: packageVersion,
description: packageDescription,
license,
repository,
flags
} = this.package.dependencyVersion;

const [nameDomElement, versionDomElement, descriptionDomElement, linksDomElement, flagsDomElement] = [
clone.querySelector(".name"),
clone.querySelector(".version"),
clone.querySelector(".package-description"),
clone.querySelector(".package-links"),
clone.querySelector(".package-flags")
]

// Name and Version
nameDomElement.textContent = packageName;
if (packageName.length >= 18) {
nameDomElement.classList.add("lowsize");
}
versionDomElement.textContent = `v${packageVersion}`;

// Description
const description = packageDescription.trim();
if (description === "") {
descriptionDomElement.style.display = "none";
}
else {
descriptionDomElement.textContent = description;
}

// Links
const packageHomePage = this.package.dependency.metadata.homepage || null;
const packageGithubPage = utils.parseRepositoryUrl(
repository,
packageHomePage !== null && new URL(packageHomePage).hostname === "github.com" ? packageHomePage : null
);

const hasNoLicense = license === "unkown license";
const links = {
npm: {
href: `https://www.npmjs.com/package/${packageName}/v/${packageVersion}`,
text: "NPM",
image: "npm-icon.svg",
showInHeader: true
},
homepage: {
href: packageHomePage,
showInHeader: false
},
github: {
href: packageGithubPage,
text: "GitHub",
image: "github-mark.png",
showInHeader: true
},
unpkg: {
href: `https://unpkg.com/${packageName}@${packageVersion}/`,
text: "Unpkg",
icon: "icon-cubes",
showInHeader: true
},
license: {
href: hasNoLicense ? "#" : (license.licenses[0]?.spdxLicenseLinks[0] ?? "#"),
text: hasNoLicense ? "unkown" : license.uniqueLicenseIds.join(", ").toUpperCase(),
icon: "icon-vcard",
showInHeader: true
},
thirdParty: {
menu: this.renderToolsMenu(packageName),
text: 'Tools',
icon: 'icon-link',
showInHeader: true
}
};
linksDomElement.appendChild(this.renderLinks(links));

// Flags
const flagFragment = this.renderFlags(flags);
if (flagFragment) {
flagsDomElement.appendChild(flagFragment);
}
else {
flagsDomElement.style.display = "none";
}

return links;
}

renderLinks(links) {
const fragment = document.createDocumentFragment();
for (const [linkName, linkAttributes] of Object.entries(links)) {
if (!linkAttributes.showInHeader || linkAttributes.href === null) {
continue;
}

const linkImageOrIcon = linkAttributes.icon ?
utils.createDOMElement("i", { classList: [linkAttributes.icon] }) :
utils.createDOMElement("img", {
attributes: { src: linkAttributes.image, alt: linkName }
});

const linksChildren = [
linkImageOrIcon,
];
if (linkAttributes.menu) {
linksChildren.push(
utils.createDOMElement("div", {
classList: ['package-info-header-menu'],
childs: linkAttributes.menu
})
);
}
else {
linksChildren.push(
utils.createDOMElement("a", {
text: linkAttributes.text,
attributes: {
href: linkAttributes.href,
target: "_blank",
rel: "noopener noreferrer"
}
})
);
}

fragment.appendChild(utils.createDOMElement("div", {
className: "link", childs: linksChildren
}));
}

return fragment;
}

/**
* @param {!string} packageName
* @returns {HTMLElement[]}
*/
renderToolsMenu(packageName) {
const { snykAdvisor, socket } = PackageHeader.ExternalLinks;

return [
utils.createDOMElement('span', { text: 'Tools' }),
utils.createDOMElement('div', {
classList: ['tools-menu'],
childs: [
utils.createDOMElement('a', {
text: 'Snyk',
attributes: {
href: snykAdvisor + packageName,
target: "_blank",
}
}),
utils.createDOMElement('a', {
text: 'Socket.dev',
attributes: {
href: socket + packageName,
target: "_blank",
}
})
]
})
]
}

renderFlags(flags) {
const textContent = getFlagsEmojisInlined(flags, new Set(window.settings.config.ignore.flags));

if (textContent === "") {
return null;
}

const flagsMap = new Map(
Object
.entries(this.package.nsn.secureDataSet.FLAGS)
.map(([name, row]) => [row.emoji, { ...row, name }])
);

const fragment = document.createDocumentFragment();
for (const icon of textContent) {
if (flagsMap.has(icon)) {
const tooltipElement = utils.createTooltip(icon, flagsMap.get(icon).tooltipDescription);
tooltipElement.addEventListener("click", () => {
const { name } = flagsMap.get(icon);

wiki.header.setNewActiveView("flags");
wiki.navigation.flags.setNewActiveMenu(name);
wiki.open();
});

fragment.appendChild(tooltipElement);
}
}

return fragment;
}
}
120 changes: 120 additions & 0 deletions public/js/components/package/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Import Internal Dependencies
import { Bundlephobia } from "./bundlephobia.js";
import { PackageHeader } from "./header.js";
import * as Pannels from "./pannels/index.js";

export class PackageInfo {
static DOMElementName = "package-info";

static close() {
const domElement = document.getElementById(PackageInfo.DOMElementName);
if (domElement.classList.contains("slide-in")) {
domElement.setAttribute("class", "slide-out");
}
}

/**
* @param {*} dependencyVersionData
* @param {*} dependency
* @param {*} nsn
*/
constructor(
dependencyVersionData,
currentNode,
dependency,
nsn
) {
this.codeCache = new Map();
this.menus = new Map();
this.nsn = nsn;
this.currentNode = currentNode;
this.dependencyVersion = dependencyVersionData;
this.dependency = dependency;

this.initialize();
}

initialize() {
const packageHTMLElement = document.getElementById(PackageInfo.DOMElementName);
packageHTMLElement.innerHTML = "";
packageHTMLElement.appendChild(
this.render()
);
this.enableNavigation(
window.settings.config.defaultPackageMenu
);
packageHTMLElement.setAttribute("class", "slide-in");

new Bundlephobia(this.dependencyVersion.name, this.dependencyVersion.version)
.fetchDataOnHttpServer()
.catch(console.error);
}

/**
* @param {HTMLElement} navElement
* @param {number} [count=0]
*/
addNavigationSignal(navElement, count = 0) {
if (count === 0) {
navElement.classList.add("disabled");
}
else {
const counter = navElement.querySelector(".signal");
counter.style.display = "flex";
counter.appendChild(document.createTextNode(count));
}
}

/**
* @param {!string} name
* @returns {void}
*/
enableNavigation(name) {
const div = this.menus.has(name) ? this.menus.get(name) : this.menus.get("info");

const isActive = div.classList.contains("active");
const isDisabled = div.classList.contains("disabled");
const dataTitle = div.getAttribute("data-title");

if (isActive || isDisabled) {
return;
}

div.classList.add("active");
this.activeNavigation.classList.remove("active");

const targetPan = document.getElementById(`pan-${name}`);
const currentPan = document.getElementById(`pan-${this.activeNavigation.getAttribute("data-menu")}`);
targetPan.classList.remove("hidden");
currentPan.classList.add("hidden");
document.querySelector(".container-title").textContent = dataTitle;

this.activeNavigation = div;
}

render() {
const template = document.getElementById("package-info-template");
/** @type {HTMLTemplateElement} */
const clone = document.importNode(template.content, true);

this.activeNavigation = clone.querySelector(".package-navigation > div.active");
for (const div of clone.querySelectorAll(".package-navigation > div")) {
const dataMenu = div.getAttribute("data-menu");
this.menus.set(dataMenu, div);

div.addEventListener("click", () => this.enableNavigation(dataMenu));
}

this.links = new PackageHeader(this).generate(clone);

new Pannels.Overview(this).generate(clone);
new Pannels.Licenses(this).generate(clone)
new Pannels.Warnings(this).generate(clone)
new Pannels.Scripts(this).generate(clone)
new Pannels.Vulnerabilities(this).generate(clone)
new Pannels.Scorecard(this).generate(clone);
new Pannels.Files(this).generate(clone);

return clone;
}
}
53 changes: 53 additions & 0 deletions public/js/components/package/pannels/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Import Internal Dependencies
import * as utils from "../../../utils.js";

export class Files {
constructor(pkg) {
this.package = pkg;
}

/**
* @param {!HTMLTemplateElement} clone
*/
generate(clone) {
const { name, version, composition } = this.package.dependencyVersion;

const onclick = (_, fileName) => {
if (fileName === "../" || fileName === "./") {
return;
}

const cleanedFile = fileName.startsWith("./") ? fileName.slice(2) : fileName;
window
.open(`https://unpkg.com/${name}@${version}/${cleanedFile}`, "_blank")
.focus();
};

utils.createItemsList(
clone.getElementById("extensions"),
composition.extensions
);

utils.createItemsList(
clone.getElementById("tarballfiles"),
composition.files,
{ onclick, hideItems: true, hideItemsLength: 3 }
);

utils.createItemsList(
clone.getElementById("minifiedfiles"),
composition.minified,
{ onclick, hideItems: true }
);

utils.createItemsList(
clone.getElementById("internaldep"),
composition.required_files,
{
onclick,
hideItems: true,
hideItemsLength: 3
}
);
}
}
Loading

0 comments on commit 266dd87

Please sign in to comment.