diff --git a/public/components/file-box/file-box.css b/public/components/file-box/file-box.css index 15569418..4cb1659f 100644 --- a/public/components/file-box/file-box.css +++ b/public/components/file-box/file-box.css @@ -1,23 +1,3 @@ -/** - * LICENSES - */ -.box-container-licenses { - display: flex; - flex-wrap: wrap; -} - -.box-container-licenses>div { - flex-grow: 1; - flex-basis: 150px; - height: 26px; - display: flex; - align-items: center; - box-sizing: border-box; - font-weight: 500; - color: #D1C4E9; - font-family: system-ui; -} - /** * WARNINGS */ diff --git a/public/components/icon/icon.js b/public/components/icon/icon.js index 0ce51651..895049dd 100644 --- a/public/components/icon/icon.js +++ b/public/components/icon/icon.js @@ -45,6 +45,20 @@ const kIcons = { t27.648-43.008zM951.296 951.296v-658.432h-220.16v237.568q0 22.528-15.36 38.912 t-38.912 15.36h-237.568v366.592h512z"/> +`, + "info-circled": html` + ` }; @@ -65,8 +79,7 @@ export class Icon extends LitElement { `; static properties = { - name: { type: String }, - size: { type: String } + name: { type: String } }; render() { diff --git a/public/components/package/package.js b/public/components/package/package.js index 516e4d5c..aca76ede 100644 --- a/public/components/package/package.js +++ b/public/components/package/package.js @@ -93,6 +93,13 @@ export class PackageInfo { files.id = "pan-files"; files.classList.add("package-container", "hidden"); panFiles.parentElement.replaceChild(files, panFiles); + + const panLicenses = packageHTMLElement.querySelector("#pan-licenses"); + const licenses = document.createElement("package-licenses"); + licenses.package = this; + licenses.id = "pan-licenses"; + files.classList.add("package-container", "hidden"); + panLicenses.parentElement.replaceChild(licenses, panLicenses); } /** @@ -153,7 +160,6 @@ export class PackageInfo { 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); diff --git a/public/components/package/pannels/licenses/licenses.js b/public/components/package/pannels/licenses/licenses.js index e49e9ba0..2cf285c7 100644 --- a/public/components/package/pannels/licenses/licenses.js +++ b/public/components/package/pannels/licenses/licenses.js @@ -1,50 +1,124 @@ +// Import Third-party Dependencies +import { LitElement, css, html } from "lit"; +import { repeat } from "lit/directives/repeat.js"; + // Import Internal Dependencies -import * as utils from "../../../../common/utils.js"; +import { selectLicenses } from "./view-model.js"; +import { currentLang } from "../../../../common/utils.js"; import "../../../file-box/file-box.js"; +import "../../../icon/icon.js"; + +class Licenses extends LitElement { + static styles = css` + .box-container-licenses { + display: flex; + flex-wrap: wrap; + } + + .box-container-licenses>div { + flex-grow: 1; + flex-basis: 150px; + height: 26px; + display: flex; + align-items: center; + box-sizing: border-box; + font-weight: 500; + color: #D1C4E9; + font-family: system-ui; + } + + .help-dialog { + display: flex; + padding: 10px; + border-radius: 8px; + margin-bottom: 10px; + border: 2px dashed #57e1bf4a; + color: #9de157; + letter-spacing: 0.5px; + align-items: center; + } + + .help-dialog> nsecure-icon { + margin-right: 11px; + font-size: 28px; + } + + .help-dialog>p { + font-size: 14px; + font-style: italic; + } + + .help-dialog>p b { + background: #9de157; + padding: 2px 5px; + color: #000; + border-radius: 4px; + font-style: normal; + font-weight: bold; + cursor: pointer; + } -export class Licenses { - constructor(pkg) { - this.package = pkg; + .help-dialog>p b:hover { + background: var(--secondary); } - /** - * @param {!HTMLTemplateElement} clone - */ - generate(clone) { - clone.getElementById("pan-licenses") - .appendChild(this.renderLicenses()); + .help-dialog>p a { + color: inherit; + cursor: pointer; + text-decoration: underline; + font-weight: bold; } +`; + + static properties = { + package: { type: Object } + }; - renderLicenses() { + render() { const { licenses } = this.package.dependencyVersion; - const fragment = document.createDocumentFragment(); const unpkgRoot = this.package.links.unpkg.href; - const processedLicenses = new Set(); - - for (const license of licenses) { - const [licenseName, licenseLink] = Object.entries(license.licenses)[0]; - if (processedLicenses.has(licenseName)) { - continue; - } - processedLicenses.add(licenseName); - - const spdx = Object.entries(license.spdx) - .map(([key, value]) => `${value ? "✔️" : "❌"} ${key}`); - - const boxContainer = utils.createDOMElement("div", { - classList: ["box-container-licenses"], - childs: spdx.map((text) => utils.createDOMElement("div", { text })) - }); - - const box = document.createElement("file-box"); - box.title = licenseName; - box.fileName = license.from; - box.titleHref = licenseLink; - box.fileHref = `${unpkgRoot}${license.from}`; - box.appendChild(boxContainer); - fragment.appendChild(box); - } - - return fragment; + const { package_info } = window.i18n[currentLang()]; + + return html` +
+ +

+ ${package_info.helpers.spdx} + ${package_info.helpers.here} + +

+
+ ${repeat( + selectLicenses(licenses, unpkgRoot), + (license) => license, + ({ + title, + spdx, + fileName, + fileHref, + titleHref + }) => html` + +
+ ${repeat( + spdx, + (text) => text, + (text) => html` +
${text}
+ ` + )} +
+
+ ` + )} + `; } } + +customElements.define("package-licenses", Licenses); diff --git a/public/components/package/pannels/licenses/view-model.js b/public/components/package/pannels/licenses/view-model.js new file mode 100644 index 00000000..30e07d6a --- /dev/null +++ b/public/components/package/pannels/licenses/view-model.js @@ -0,0 +1,21 @@ +export function selectLicenses(licenses, unpkgRoot) { + const processedLicenses = new Set(); + + return licenses.flatMap((license) => { + const [licenseName, licenseLink] = Object.entries(license.licenses)[0]; + if (processedLicenses.has(licenseName)) { + return []; + } + processedLicenses.add(licenseName); + + return [ + { + title: licenseName, + spdx: Object.entries(license.spdx).map(([key, value]) => `${value ? "✔️" : "❌"} ${key}`), + fileName: license.from, + fileHref: `${unpkgRoot}${license.from}`, + titleHref: licenseLink + } + ]; + }); +} diff --git a/public/components/package/pannels/warnings/warnings.js b/public/components/package/pannels/warnings/warnings.js index f608e141..d29e75e2 100644 --- a/public/components/package/pannels/warnings/warnings.js +++ b/public/components/package/pannels/warnings/warnings.js @@ -118,7 +118,9 @@ export class Warnings { box.fileHref = `${unpkgRoot}${warning.file}`; box.severity = warning.severity ?? "Information"; box.appendChild(boxContainer); - box.appendChild(boxPosition); + if (boxPosition) { + box.appendChild(boxPosition); + } fragment.appendChild(box); } diff --git a/test/ui/licenses.test.js b/test/ui/licenses.test.js new file mode 100644 index 00000000..f37534a3 --- /dev/null +++ b/test/ui/licenses.test.js @@ -0,0 +1,98 @@ +// Import Node.js Dependencies +import assert from "node:assert"; +import { describe, it } from "node:test"; + +// Import Internal Dependencies +import { selectLicenses } from "../../public/components/package/pannels/licenses/view-model.js"; + +describe("select licenses", () => { + it("should have no licenses", () => { + const licenses = []; + assert.deepEqual(selectLicenses(licenses, "./"), []); + }); + + it("should select a licences with spdx ✔️", () => { + const licenses = [ + { + licenses: { + "Apache-2.0": "https://spdx.org/licenses/Apache-2.0.html#licenseText" + }, + spdx: { + osi: true, + fsf: true, + fsfAndOsi: true, + includesDeprecated: true + }, + from: "package.json" + } + ]; + assert.deepEqual(selectLicenses(licenses, "./"), [{ + title: "Apache-2.0", + spdx: ["✔️ osi", "✔️ fsf", "✔️ fsfAndOsi", "✔️ includesDeprecated"], + fileName: "package.json", + fileHref: "./package.json", + titleHref: "https://spdx.org/licenses/Apache-2.0.html#licenseText" + }]); + }); + + it("should select a licences with spdx ❌", () => { + const licenses = [ + { + licenses: { + "Apache-3.0": "https://spdx.org/licenses/Apache-3.0.html#licenseText" + }, + spdx: { + osi: false, + fsf: false, + fsfAndOsi: false, + includesDeprecated: false + }, + from: "/package.json" + } + ]; + assert.deepEqual(selectLicenses(licenses, ".."), [{ + title: "Apache-3.0", + spdx: ["❌ osi", "❌ fsf", "❌ fsfAndOsi", "❌ includesDeprecated"], + fileName: "/package.json", + fileHref: "../package.json", + titleHref: "https://spdx.org/licenses/Apache-3.0.html#licenseText" + }]); + }); + + it("should drop duplicates", () => { + const licenses = [ + { + licenses: { + "Apache-3.0": "https://spdx.org/licenses/Apache-3.0.html#licenseText" + }, + spdx: { + osi: false, + fsf: false, + fsfAndOsi: false, + includesDeprecated: false + }, + from: "/package.json" + }, + { + licenses: { + "Apache-3.0": "https://spdx.org/licenses/Apache-3.0.html#licenseText" + }, + spdx: { + osi: false, + fsf: false, + fsfAndOsi: false, + includesDeprecated: false + }, + from: "/package.json" + } + ]; + + assert.deepEqual(selectLicenses(licenses, ".."), [{ + title: "Apache-3.0", + spdx: ["❌ osi", "❌ fsf", "❌ fsfAndOsi", "❌ includesDeprecated"], + fileName: "/package.json", + fileHref: "../package.json", + titleHref: "https://spdx.org/licenses/Apache-3.0.html#licenseText" + }]); + }); +});