From 3ec54c3c6a59611dba9b9ad848d1234bd36095e9 Mon Sep 17 00:00:00 2001 From: cgombauld Date: Sun, 17 Aug 2025 18:22:45 +0200 Subject: [PATCH 1/2] refactor(report): migrate to lit.js --- public/components/views/home/home.css | 1 - public/components/views/home/home.js | 9 +- .../components/views/home/report/report.css | 138 -------- .../components/views/home/report/report.html | 26 -- public/components/views/home/report/report.js | 311 +++++++++++++++--- 5 files changed, 271 insertions(+), 214 deletions(-) delete mode 100644 public/components/views/home/report/report.css delete mode 100644 public/components/views/home/report/report.html diff --git a/public/components/views/home/home.css b/public/components/views/home/home.css index e110cc19..324dcc87 100644 --- a/public/components/views/home/home.css +++ b/public/components/views/home/home.css @@ -1,5 +1,4 @@ @import url("./maintainers/maintainers.css"); -@import url("./report/report.css"); #home--view { z-index: 10; diff --git a/public/components/views/home/home.js b/public/components/views/home/home.js index 39268c62..5f160d6b 100644 --- a/public/components/views/home/home.js +++ b/public/components/views/home/home.js @@ -12,7 +12,7 @@ import { fetchScorecardData, getScorecardLink } from "../../../common/scorecard. // Import Components import { Maintainers } from "./maintainers/maintainers.js"; -import { PopupReport } from "./report/report.js"; +import "./report/report.js"; // CONSTANTS const kFlagsToWatch = new Set([ @@ -390,9 +390,14 @@ export class HomeView { handleReport() { document.querySelector(".home--header--report").addEventListener("click", async() => { + const popupReport = document.createElement("popup-report"); + popupReport.rootDependencyName = this.secureDataSet.data.rootDependencyName; + popupReport.theme = this.secureDataSet.theme; + const fragment = document.createDocumentFragment(); + fragment.appendChild(popupReport); window.dispatchEvent(new CustomEvent(EVENTS.MODAL_OPENED, { detail: { - content: new PopupReport(this.secureDataSet.data.rootDependencyName).render() + content: fragment } })); }); diff --git a/public/components/views/home/report/report.css b/public/components/views/home/report/report.css deleted file mode 100644 index 4c41189a..00000000 --- a/public/components/views/home/report/report.css +++ /dev/null @@ -1,138 +0,0 @@ -/* FIXME: remove the !important when the popup is migrated to lit */ - -.report--popup { - min-width: 400px; - padding: 40px; - display: flex; - flex-direction: column; -} - -.report--popup>.title { - height: 2px; - background: #d3d3d387; - margin: 0 10px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.popup-dark>.title { - background: var(--dark-theme-secondary-color) !important; -} - -.report--popup>.title>p { - background: #f5f4f4; - padding: 0 10px; - font-family: roboto; - font-weight: bold; - letter-spacing: 1.2px; - color: #255471; - font-size: 20px; -} - -.popup-dark>.title>p { - background: #303263 !important; - color: #3cbde5 !important; -} - -.report--popup>form { - display: flex; - flex-direction: column; - padding: 20px; - padding-bottom: 0; -} - -.report--popup>form label { - color: #546884; - margin-bottom: 8px; - font-weight: 500; - font-size: 18px; -} - -.popup-dark>form label { - color: white !important; -} - -.report--popup>form input { - padding: 11px 6px; - border: none; - border-left: 4px solid #546884; - margin-bottom: 10px; - border-radius: 2px; - box-shadow: 0 3px 7px 1px rgb(0 0 0 / 10%); - font-size: 16px; -} - -.report--popup>form>button { - border: none; - padding: 8px; - color: white; - background: #43a82f; - font-weight: bold; - cursor: pointer; - width: 120px; - margin: auto; - margin-top: 20px; - font-size: 16px; - border-radius: 4px; -} - -.report--popup .spinner { - width: 7px; - height: 7px; - display: inline-block; - border-radius: 50%; - margin-right: 10px; - border: 3px solid white; - animation: spinner-from 0.8s infinite linear alternate, spinner-to 1.6s infinite linear; -} - - -@keyframes spinner-from { - 0% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%); - } - - 12.5% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%); - } - - 25% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%); - } - - 50% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); - } - - 62.5% { - clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); - } - - 75% { - clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%); - } - - 100% { - clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%); - } -} - -@keyframes spinner-to { - 0% { - transform: scaleY(1) rotate(0deg); - } - - 49.99% { - transform: scaleY(1) rotate(135deg); - } - - 50% { - transform: scaleY(-1) rotate(0deg); - } - - 100% { - transform: scaleY(-1) rotate(-135deg); - } -} diff --git a/public/components/views/home/report/report.html b/public/components/views/home/report/report.html deleted file mode 100644 index 005c288b..00000000 --- a/public/components/views/home/report/report.html +++ /dev/null @@ -1,26 +0,0 @@ - diff --git a/public/components/views/home/report/report.js b/public/components/views/home/report/report.js index 48571b9a..76e11743 100644 --- a/public/components/views/home/report/report.js +++ b/public/components/views/home/report/report.js @@ -1,55 +1,272 @@ +// Import Third-party Dependencies +import { LitElement, html, css, nothing } from "lit"; +import { when } from "lit/directives/when.js"; -export class PopupReport { - constructor(rootDependencyName) { - this.rootDependencyName = rootDependencyName; +// Import Internal Dependencies +import { EVENTS } from "../../../../core/events"; +import { currentLang } from "../../../../common/utils"; + +class PopupReport extends LitElement { + static styles = css` +.report--popup { + min-width: 400px; + padding: 40px; + display: flex; + flex-direction: column; +} + +.report--popup>.title { + height: 2px; + margin: 0 10px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + + +.light >.title { + background: #d3d3d387; +} +.dark >.title { + background: var(--dark-theme-secondary-color); +} + +.report--popup>.title>p { + padding: 0 10px; + font-family: roboto; + font-weight: bold; + letter-spacing: 1.2px; + font-size: 20px; +} + +.dark .title>p { + background: #303263; + color: #3cbde5; +} + +.light .title>p { + background: #f5f4f4; + color: #255471; +} + +.report--popup>form { + display: flex; + flex-direction: column; + padding: 20px; + padding-bottom: 0; +} + +.report--popup>form label { + margin-bottom: 8px; + font-weight: 500; + font-size: 18px; +} + +.dark >form label { + color: white; +} + +.light >form label { + color: #546884; +} + +.report--popup>form input { + padding: 11px 6px; + border: none; + border-left: 4px solid #546884; + margin-bottom: 10px; + border-radius: 2px; + box-shadow: 0 3px 7px 1px rgb(0 0 0 / 10%); + font-size: 16px; +} + +.report--popup>form>button { + border: none; + padding: 8px; + color: white; + background: #43a82f; + font-weight: bold; + cursor: pointer; + width: 120px; + margin: auto; + margin-top: 20px; + font-size: 16px; + border-radius: 4px; +} + +.report--popup .spinner { + width: 7px; + height: 7px; + display: inline-block; + border-radius: 50%; + margin-right: 10px; + border: 3px solid white; + animation: spinner-from 0.8s infinite linear alternate, spinner-to 1.6s infinite linear; +} + + +@keyframes spinner-from { + 0% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%); } - render() { - const templateElement = document.getElementById("report-popup-template"); - /** @type {HTMLElement} */ - const clone = templateElement.content.cloneNode(true); - const form = clone.querySelector("form"); + 12.5% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%); + } + + 25% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%); + } + + 50% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); + } + + 62.5% { + clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); + } + + 75% { + clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%); + } + + 100% { + clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%); + } +} + +@keyframes spinner-to { + 0% { + transform: scaleY(1) rotate(0deg); + } + + 49.99% { + transform: scaleY(1) rotate(135deg); + } + + 50% { + transform: scaleY(-1) rotate(0deg); + } + + 100% { + transform: scaleY(-1) rotate(-135deg); + } +} +`; + + static properties = { + theme: { type: String }, + rootDependencyName: { type: String }, + isLoading: { type: Boolean } + }; + + constructor() { + super(); + this.isLoading = false; + this.settingsChanged = ({ detail: { theme } }) => { + if (theme !== this.theme) { + this.theme = theme; + } + }; + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener(EVENTS.SETTINGS_SAVED, this.settingsChanged); + } + + disconnectedCallback() { + window.removeEventListener(EVENTS.SETTINGS_SAVED, this.settingsChanged); + super.disconnectedCallback(); + } + + firstUpdated() { const isLightPreference = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches; + if (isLightPreference) { + this.renderRoot.querySelector("#lightTheme").checked = true; + } + else { + this.renderRoot.querySelector("#darkTheme").checked = true; + } + } + + render() { + const { popup: { report } } = window.i18n[currentLang()]; const defaultTitle = `${this.rootDependencyName}'s report`; - form.querySelector("#lightTheme").checked = isLightPreference; - form.querySelector("#darkTheme").checked = !isLightPreference; - clone.querySelector("#title").placeholder = defaultTitle; - form.addEventListener("submit", (e) => { - e.preventDefault(); - - form.querySelector(".spinner").classList.remove("hidden"); - const title = form.querySelector("#title").value || defaultTitle; - const theme = form.querySelector("#lightTheme").checked ? "light" : "dark"; - const includesAllDeps = form.querySelector("#includesAllDeps").checked; - - fetch("/report", { - method: "POST", - body: JSON.stringify({ - title, - includesAllDeps, - theme - }), - headers: { - "Content-Type": "application/json" + + return html` +
+
+

${report.title}

+
+
+ + { + e.stopPropagation(); + }} placeholder=${defaultTitle} type="text" id="title" name="title"> +
+ + +
+
+ + +
+
+ + +
+ +
+
+ `; } + + handleSubmit = (e) => { + e.preventDefault(); + if (this.isLoading) { + return; + } + this.isLoading = true; + const formData = new FormData(e.target); + const title = formData.get("title") || `${this.rootDependencyName} 's report`; + const theme = formData.get("theme"); + const includesAllDeps = formData.get("includesAllDeps") === "includesAllDeps"; + + fetch("/report", { + method: "POST", + body: JSON.stringify({ + title, + includesAllDeps, + theme + }), + headers: { + "Content-Type": "application/json" + } + }).then(async(response) => { + const { data: json } = await response.json(); + const url = window.URL.createObjectURL( + new Blob( + [new Uint8Array(json.data).buffer], { type: "application/pdf" } + ) + ); + const link = document.createElement("a"); + link.href = url; + link.target = "_blank"; + link.download = `${title}.pdf`; + document.body.appendChild(link); + link.click(); + }).finally(() => { + this.isLoading = false; + }); + }; } + +customElements.define("popup-report", PopupReport); From 72934dba9d96a79b033f39e5a0657265649c06eb Mon Sep 17 00:00:00 2001 From: Clement Gombauld Date: Mon, 18 Aug 2025 10:33:12 +0200 Subject: [PATCH 2/2] Update public/components/views/home/report/report.js Co-authored-by: PierreDemailly <39910767+PierreDemailly@users.noreply.github.com> --- public/components/views/home/report/report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/components/views/home/report/report.js b/public/components/views/home/report/report.js index 76e11743..6f88a3a7 100644 --- a/public/components/views/home/report/report.js +++ b/public/components/views/home/report/report.js @@ -3,8 +3,8 @@ import { LitElement, html, css, nothing } from "lit"; import { when } from "lit/directives/when.js"; // Import Internal Dependencies -import { EVENTS } from "../../../../core/events"; -import { currentLang } from "../../../../common/utils"; +import { EVENTS } from "../../../../core/events.js"; +import { currentLang } from "../../../../common/utils.js"; class PopupReport extends LitElement { static styles = css`