diff --git a/public/common/utils.js b/public/common/utils.js index f7e7bc0c..cdae42c3 100644 --- a/public/common/utils.js +++ b/public/common/utils.js @@ -1,5 +1,4 @@ // Import Internal Dependencies -import avatarURL from "../img/avatar-default.png"; import "../components/expandable/expandable.js"; window.activeLegendElement = null; @@ -108,43 +107,6 @@ export function parseRepositoryUrl(repository = {}, defaultValue = null) { } } -function createImageElement(baseUrl, id = null) { - const imageElement = document.createElement("img"); - if (id === null || id === "") { - imageElement.src = `${avatarURL}`; - } - else { - imageElement.src = `${baseUrl}/${id}`; - imageElement.onerror = () => { - imageElement.src = `${avatarURL}`; - }; - } - - return imageElement; -} - -export function createAvatarImageElementForAuthor(author = {}) { - return author.npmAvatar - ? createImageElement("https://www.npmjs.com", author.npmAvatar) - : createImageElement("https://unavatar.io", author.email); -} - -export function createAvatar(name, desc) { - const pElement = createDOMElement("p", { - classList: ["count"], text: desc.count - }); - const aElement = createLink(desc.url || "#"); - const divEl = createDOMElement("div", { - classList: ["avatar"], childs: [pElement, aElement] - }); - - const imgEl = createAvatarImageElementForAuthor({ email: desc.email }); - imgEl.alt = name; - aElement.appendChild(imgEl); - - return divEl; -} - export function createLiField(title, value, options = {}) { const { isLink = false } = options; diff --git a/public/components/icon/icon.js b/public/components/icon/icon.js index fcd29d87..93dc7577 100644 --- a/public/components/icon/icon.js +++ b/public/components/icon/icon.js @@ -108,7 +108,28 @@ const kIcons = { 156.672 0 265.216-108.544t108.544-263.168q0-156.672-108.544-265.216l-37.888-36.864 109.568-109.568 q15.36-15.36 37.888-15.36t37.888 15.36 14.336 37.888-15.36 37.888l-35.84 35.84q104.448 131.072 104.448 300.032 0 197.632-140.288 338.944-99.328 98.304-232.448 128v65.536h212.992z"/> - ` + `, + cube: html` + + `, + users: html` + +` }; export class Icon extends LitElement { diff --git a/public/components/npm-avatar/npm-avatar.js b/public/components/npm-avatar/npm-avatar.js new file mode 100644 index 00000000..be917c03 --- /dev/null +++ b/public/components/npm-avatar/npm-avatar.js @@ -0,0 +1,30 @@ +// Import Third-party Dependencies +import { LitElement, html } from "lit"; +import { when } from "lit/directives/when.js"; + +// Import Internal Dependencies +import avatarURL from "../../img/avatar-default.png"; + +export class NpmAvatar extends LitElement { + static properties = { + imgStyle: { type: String }, + avatar: { type: String }, + email: { type: String } + }; + + render() { + return when( + this.avatar, + () => html` { + e.currentTarget.src = avatarURL; + }}>`, + () => html` { + e.currentTarget.src = avatarURL; + }}>` + ); + } +} + +customElements.define("npm-avatar", NpmAvatar); diff --git a/public/components/package/pannels/overview/overview.js b/public/components/package/pannels/overview/overview.js index 76d83649..08f90204 100644 --- a/public/components/package/pannels/overview/overview.js +++ b/public/components/package/pannels/overview/overview.js @@ -5,6 +5,7 @@ import prettyBytes from "pretty-bytes"; import * as utils from "../../../../common/utils.js"; import { PopupMaintainer } from "../../../views/home/maintainers/maintainers.js"; import { EVENTS } from "../../../../core/events.js"; +import "../../../npm-avatar/npm-avatar.js"; // CONSTANTS const kEnGBDateFormat = Intl.DateTimeFormat("en-GB", { @@ -219,9 +220,13 @@ export class Overview { const fragment = document.createDocumentFragment(); for (const author of metadata.maintainers) { + const authorAvatar = document.createElement("npm-avatar"); + authorAvatar.avatar = author.npmAvatar; + authorAvatar.email = author.email; + authorAvatar.imgStyle = "width: 40px; margin-right: 6px;"; const divElement = utils.createDOMElement("div", { childs: [ - utils.createAvatarImageElementForAuthor(author), + authorAvatar, utils.createDOMElement("p", { text: author.name }), diff --git a/public/components/views/home/home.css b/public/components/views/home/home.css index 324dcc87..ca27fdce 100644 --- a/public/components/views/home/home.css +++ b/public/components/views/home/home.css @@ -1,5 +1,3 @@ -@import url("./maintainers/maintainers.css"); - #home--view { z-index: 10; flex-direction: column; diff --git a/public/components/views/home/home.html b/public/components/views/home/home.html index 8b5e4932..9cba4082 100644 --- a/public/components/views/home/home.html +++ b/public/components/views/home/home.html @@ -65,17 +65,7 @@
-
-
-
- -

[[=z.token('home.maintainers')]]

- 0 -
-
-
-
-
+
diff --git a/public/components/views/home/home.js b/public/components/views/home/home.js index d40ee3a6..5b678b99 100644 --- a/public/components/views/home/home.js +++ b/public/components/views/home/home.js @@ -11,7 +11,7 @@ import { EVENTS } from "../../../core/events.js"; import { fetchScorecardData, getScorecardLink } from "../../../common/scorecard.js"; // Import Components -import { Maintainers } from "./maintainers/maintainers.js"; +import "./maintainers/maintainers.js"; import "./report/report.js"; // CONSTANTS @@ -332,8 +332,15 @@ export class HomeView { } generateMaintainers() { - new Maintainers(this.secureDataSet, this.nsn) - .render(); + const maintainers = document.createElement("nsecure-maintainers"); + maintainers.secureDataSet = this.secureDataSet; + maintainers.nsn = this.nsn; + maintainers.theme = this.secureDataSet.theme; + maintainers.options = { + maximumMaintainers: 5 + }; + const pannel = document.getElementById("pannel-right"); + pannel.prepend(maintainers); } generateModuleTypes() { diff --git a/public/components/views/home/maintainers/maintainers.css b/public/components/views/home/maintainers/maintainers.css deleted file mode 100644 index 99448906..00000000 --- a/public/components/views/home/maintainers/maintainers.css +++ /dev/null @@ -1,85 +0,0 @@ -.home--maintainers { - display: flex; - flex-wrap: wrap; - margin-left: -10px; - margin-top: -10px; -} - -.home--maintainers>.person { - height: 65px; - flex-basis: 330px; - background: linear-gradient(to bottom, rgb(255 255 255) 0%, rgb(245 252 255) 100%); - display: flex; - position: relative; - box-sizing: border-box; - border-radius: 4px; - overflow: hidden; - margin-left: 10px; - margin-top: 10px; - box-shadow: 1px 1px 4px 0 #271e792b; - color: #546884; - flex-grow: 1; -} - -body.dark .home--maintainers>.person { - color: white; - background: var(--dark-theme-primary-color); -} - -.home--maintainers> .highlighted{ - background: linear-gradient(to bottom, rgb(230 240 250) 0%, rgb(220 235 245) 100%); -} - -body.dark .home--maintainers > .highlighted { - background: linear-gradient(to right, rgb(11 3 31) 0%, rgb(46 10 10 / 80%) 100%); -} - -.home--maintainers>.person:hover { - border-color: var(--secondary-darker); - cursor: pointer; -} - -.home--maintainers>.person>img { - width: 65px; - flex-shrink: 0; -} - -.home--maintainers>.person>i { - margin-right: 10px; - display: flex; - align-items: center; - color: #1976D2; - font-size: 18px; -} - -.home--maintainers>.person>.whois { - display: flex; - flex-direction: column; - justify-content: center; - margin-left: 10px; - font-size: 15px; - font-family: mononoki; - flex-grow: 1; - margin-right: 10px; -} - -.home--maintainers>.person>.whois>span { - color: var(--secondary-darker); - font-size: 14px; - margin-top: 5px; - font-family: monospace; -} - -.home--maintainers>.person>div.packagescount { - display: flex; - align-items: center; - font-family: mononoki; - font-size: 18px; - margin-right: 15px; - flex-basis: 40px; - flex-shrink: 0; -} - -.home--maintainers>.person>div.packagescount>i { - margin-right: 4px; -} diff --git a/public/components/views/home/maintainers/maintainers.js b/public/components/views/home/maintainers/maintainers.js index 4d6f1dd3..2ff6d423 100644 --- a/public/components/views/home/maintainers/maintainers.js +++ b/public/components/views/home/maintainers/maintainers.js @@ -2,114 +2,302 @@ import { LitElement, html, css, nothing } from "lit"; import { when } from "lit/directives/when.js"; import { repeat } from "lit/directives/repeat.js"; +import { classMap } from "lit/directives/class-map.js"; // Import Internal Dependencies import * as utils from "../../../../common/utils.js"; import "../../../expandable/expandable.js"; import { EVENTS } from "../../../../core/events.js"; import "../../../icon/icon.js"; -import avatarURL from "../../../../img/avatar-default.png"; - -export class Maintainers { - static whois(name, email) { - const childs = [ - utils.createDOMElement("p", { text: name }) - ]; - if (typeof email === "string") { - childs.push(utils.createDOMElement("span", { text: email })); - } +import "../../../npm-avatar/npm-avatar.js"; + +export class Maintainers extends LitElement { + static styles = css` + +.module { + display: flex; + flex-direction: column; +} + +.title{ + height: 34px; + display: flex; + background: rgb(55 34 175); + background: linear-gradient(-45deg, rgb(55 34 175 / 100%) 0%, + rgb(55 34 175 / 100%) 48%, rgb(90 68 218 / 100%) 75%, rgb(90 68 218 / 100%) 100%); + background: linear-gradient(-45deg, rgb(55 34 175 / 100%) 0%, + rgb(55 34 175 / 100%) 48%, rgb(90 68 218 / 100%) 75%, rgb(90 68 218 / 100%) 100%); + background: linear-gradient(135deg, rgb(55 34 175 / 100%) 0%, + rgb(55 34 175 / 100%) 48%, rgb(90 68 218 / 100%) 75%, rgb(90 68 218 / 100%) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3722af', endColorstr='#5a44da', GradientType=1); + margin-bottom: 10px; + box-sizing: border-box; + border-radius: 4px; + padding: 0 10px; + align-items: center; + font-family: mononoki; + text-shadow: 1px 1px 10px #05a0ff6b; +} - return utils.createDOMElement("div", { - className: "whois", childs - }); + .users { + width: fit-content; + transform: scale(2); + display: flex; + padding: 0px; + justify-content: center; + align-items: center; + margin: 0px; + margin-right: 6px; + margin-left: 15px; + margin-top: 18px; +} + +.link { + display: flex; + align-items: center; + margin-right: 10px; +} + +.link nsecure-icon { + color: rgb(25, 118, 210); +} + +.count{ +width: 16px; + height: 16px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: #FDD835; + margin-left: 10px; + color: #263238; + font-size: 12px; + font-weight: bold; + padding: 4px; +} + + .home--maintainers { + display: flex; + flex-wrap: wrap; + margin-left: -10px; + margin-top: -10px; +} + +.name { + margin: 0px; +} + +.home--maintainers>.person { + height: 65px; + flex-basis: 330px; + background: linear-gradient(to bottom, rgb(255 255 255) 0%, rgb(245 252 255) 100%); + display: flex; + position: relative; + box-sizing: border-box; + border-radius: 4px; + overflow: hidden; + margin-left: 10px; + margin-top: 10px; + box-shadow: 1px 1px 4px 0 #271e792b; + color: #546884; + flex-grow: 1; +} + +.dark .home--maintainers>.person { + color: white; + background: var(--dark-theme-primary-color); +} + +.home--maintainers> .highlighted{ + background: linear-gradient(to bottom, rgb(230 240 250) 0%, rgb(220 235 245) 100%); +} + +.dark .home--maintainers > .highlighted { + background: linear-gradient(to right, rgb(11 3 31) 0%, rgb(46 10 10 / 80%) 100%); +} + +.home--maintainers>.person:hover { + border-color: var(--secondary-darker); + cursor: pointer; +} + +.home--maintainers>.person>nsecure-icon { + margin-right: 10px; + display: flex; + align-items: center; + color: #1976D2; + font-size: 18px; +} + +.home--maintainers>.person>.whois { + display: flex; + flex-direction: column; + justify-content: center; + margin-left: 10px; + font-size: 15px; + font-family: mononoki; + flex-grow: 1; + margin-right: 10px; +} + +.home--maintainers>.person>.whois>span { + color: var(--secondary-darker); + font-size: 14px; + margin-top: 5px; + font-family: monospace; +} + +.home--maintainers>.person>div.packagescount { + display: flex; + align-items: center; + font-family: mononoki; + font-size: 18px; + margin-right: 15px; + flex-basis: 40px; + flex-shrink: 0; +} + +.home--maintainers>.person>div.packagescount>nsecure-icon { + margin-right: 4px; +} +`; + + static properties = { + secureDataSet: { type: Object }, + nsn: { type: Object }, + options: { type: Object }, + isClosed: { type: Boolean }, + theme: { type: String } + }; + + constructor() { + super(); + this.isClosed = true; + this.settingsChanged = ({ detail: { theme } }) => { + if (theme !== this.theme) { + this.theme = theme; + } + }; } - constructor(secureDataSet, nsn, options = {}) { - const { maximumMaintainers = 5 } = options; + connectedCallback() { + super.connectedCallback(); + window.addEventListener(EVENTS.SETTINGS_SAVED, this.settingsChanged); + } - this.secureDataSet = secureDataSet; - this.nsn = nsn; - this.maximumMaintainers = maximumMaintainers; + disconnectedCallback() { + window.removeEventListener(EVENTS.SETTINGS_SAVED, this.settingsChanged); + super.disconnectedCallback(); } render() { const authors = this.#highlightContacts([...this.secureDataSet.authors.entries()] .sort((left, right) => right[1].packages.size - left[1].packages.size)); - document.getElementById("authors-count").innerHTML = authors.length; - const maintainers = document.querySelector(".home--maintainers"); - this.generate(authors, maintainers); - } + const { maximumMaintainers } = this.options; - #highlightContacts(authors) { - const highlightedAuthors = authors - .filter(([_, contact]) => this.secureDataSet.isHighlighted(contact)); + const hideItems = authors.length > maximumMaintainers; - const authorsRest = authors.filter(([_, contact]) => !this.secureDataSet.isHighlighted(contact)); + const numOfMaintainers = this.isClosed ? maximumMaintainers : authors.length; - return [...highlightedAuthors, ...authorsRest]; - } + const visibleAuthors = hideItems ? authors.slice(0, numOfMaintainers) : authors; - generate(authors, maintainers) { - const fragment = document.createDocumentFragment(); - const hideItems = authors.length > this.maximumMaintainers; + const i18n = window.i18n[utils.currentLang()]; - for (let id = 0; id < authors.length; id++) { - const [name, data] = authors[id]; - if (typeof name === "undefined") { - continue; - } - const { packages, email, url = null } = data; - - const hasURL = typeof url === "string"; - const person = utils.createDOMElement("div", { - className: "person", - childs: [ - utils.createAvatarImageElementForAuthor(data), - Maintainers.whois(name, email), - hasURL ? utils.createDOMElement("i", { className: "icon-link" }) : null, - utils.createDOMElement("div", { - className: "packagescount", - childs: [ - utils.createDOMElement("i", { className: "icon-cube" }), - utils.createDOMElement("p", { text: packages.size }) - ] - }) - ] - }); - if (this.secureDataSet.isHighlighted(data)) { - person.classList.add("highlighted"); - } - if (hideItems && id >= this.maximumMaintainers) { - person.classList.add("hidden"); - } - person.addEventListener("click", () => { - // TODO: close package info? - const popupMaintainer = document.createElement("popup-maintainer"); - popupMaintainer.data = data; - popupMaintainer.theme = this.secureDataSet.theme; - popupMaintainer.nsn = this.nsn; - popupMaintainer.name = name; - window.dispatchEvent(new CustomEvent(EVENTS.MODAL_OPENED, { - detail: { - content: popupMaintainer - } - })); - }); + return html` +
+
+ +

${i18n.home.maintainers}

+ ${authors.length} +
+
+
+ ${this.#generateMaintainers(visibleAuthors)} + +
+ ${when(hideItems, + () => html` { + this.isClosed = !this.isClosed; + }}>`, + () => nothing)} +
+
+ `; + } - fragment.appendChild(person); + #generateMaintainers(authors) { + return html` + ${repeat(authors.filter(([name]) => typeof name != "undefined"), + (author) => author, + ([name, data]) => { + const { packages, email, url = null, npmAvatar } = data; + const personClasses = { + person: true, + highlighted: this.secureDataSet.isHighlighted(data) + }; + + return html` +
{ + // TODO: close package info? + const popupMaintainer = document.createElement("popup-maintainer"); + popupMaintainer.data = data; + popupMaintainer.theme = this.secureDataSet.theme; + popupMaintainer.nsn = this.nsn; + popupMaintainer.name = name; + window.dispatchEvent(new CustomEvent(EVENTS.MODAL_OPENED, { + detail: { + content: popupMaintainer + } + })); + }} + class="${classMap(personClasses)}"> + +
+

${name}

+ ${when( + typeof email === "string", + () => html``, + () => nothing + )} +
+ ${when( + typeof url === "string", + () => html` + + ` + )} +
+ +

${packages.size}

+
+
+ `; + } + ) } +`; + } - maintainers.appendChild(fragment); - if (hideItems) { - const expandableSpan = document.createElement("expandable-span"); - expandableSpan.onToggle = (expandable) => utils.toggle(expandable, maintainers, this.maximumMaintainers); + #highlightContacts(authors) { + const highlightedAuthors = authors + .filter(([_, contact]) => this.secureDataSet.isHighlighted(contact)); - maintainers.appendChild(expandableSpan); - } + const authorsRest = authors.filter(([_, contact]) => !this.secureDataSet.isHighlighted(contact)); + + return [...highlightedAuthors, ...authorsRest]; } } +customElements.define("nsecure-maintainers", Maintainers); + export class PopupMaintainer extends LitElement { static styles = css` .maintainers--popup { @@ -138,10 +326,6 @@ export class PopupMaintainer extends LitElement { box-shadow: 2px 2px 6px 0 #00000012; } -.maintainers--popup>.header>.avatar>img { - width: 80px; -} - .maintainers--popup>.header>.informations { display: flex; flex-direction: column; @@ -346,18 +530,9 @@ export class PopupMaintainer extends LitElement {
- ${when( - this.data.npmAvatar, - () => html` { - e.currentTarget.src = avatarURL; - }}>`, - () => html` { - e.currentTarget.src = avatarURL; - }}>` - ) - } +