diff --git a/.changeset/easy-schools-unite.md b/.changeset/easy-schools-unite.md new file mode 100644 index 00000000..a7bdbdd3 --- /dev/null +++ b/.changeset/easy-schools-unite.md @@ -0,0 +1,5 @@ +--- +"@nodesecure/cli": major +--- + +Enable checkJS and add missing JSDoc and fix all application errors diff --git a/eslint.config.mjs b/eslint.config.mjs index cec7a9f1..4dcab742 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,10 +10,13 @@ export default [ jsdoc }, rules: { - "jsdoc/no-undefined-types": ["warn", { - disableReporting: true, - markVariablesAsUsed: true - }] + "jsdoc/no-undefined-types": [ + "warn", { + disableReporting: true, + markVariablesAsUsed: true + } + ], + "no-inline-comments": "off" } }, { diff --git a/workspaces/cli/package.json b/workspaces/cli/package.json index b0f34911..31571c56 100644 --- a/workspaces/cli/package.json +++ b/workspaces/cli/package.json @@ -69,6 +69,7 @@ "@openally/httpie": "1.1.2", "@playwright/test": "1.61.1", "@stylistic/stylelint-plugin": "5.1.0", + "@types/semver": "7.7.1", "esbuild": "0.28.1", "highlight.js": "11.11.1", "postcss-lit": "1.4.1", diff --git a/workspaces/cli/public/common/utils.js b/workspaces/cli/public/common/utils.js index 2a05c467..d72851cc 100644 --- a/workspaces/cli/public/common/utils.js +++ b/workspaces/cli/public/common/utils.js @@ -31,7 +31,7 @@ export function extractEmojis(strWithEmojis) { * @param {keyof HTMLElementTagNameMap} kind * @param {object} [options] * @param {string[]} [options.classList] - * @param {HTMLElement[]} [options.childs] + * @param {(Node | null)[]} [options.childs] * @param {Record} [options.attributes] * @param {Record} [options.styles] * @param {string | null} [options.text] @@ -136,7 +136,7 @@ export function parseRepositoryUrl(repository = {}, defaultValue = null) { /** * @param {string} title - * @param {string} value + * @param {string | number} value * @param {Record} options * @returns {HTMLElement} */ @@ -148,11 +148,12 @@ export function createLiField(title, value, options = {}) { let elementToAppend; if (isLink) { - const textValue = value.length > 26 ? `${value.slice(0, 26)}...` : value; - elementToAppend = createLink(value, textValue); + const href = /** @type {string} */ (value); + const textValue = href.length > 26 ? `${href.slice(0, 26)}...` : href; + elementToAppend = createLink(href, textValue); } else { - elementToAppend = createDOMElement("p", { text: value }); + elementToAppend = createDOMElement("p", { text: String(value) }); } liElement.appendChild(elementToAppend); @@ -163,7 +164,7 @@ export function createLiField(title, value, options = {}) { * @param {HTMLElement} node - The parent DOM element. * @param {string[]} items - Array of strings to display. * @param {Object} [options] - Optional configuration options. - * @param {Function} [options.onclick] - Callback function (event, item). + * @param {(event: Event, item: string) => void} [options.onclick] - Callback function (event, item). * @param {boolean} [options.hideItems] - Hide items if needed. * @param {number} [options.hideItemsLength] - Number of visible elements before masking. * @returns {void} @@ -307,6 +308,48 @@ export function hideOnClickOutside( return outsideClickListener; } +/** + * Returns the i18n dictionary for the given language (defaults to the current language). + * + * Works around `window.i18n`'s declared type being widened to `Record` + * by a third-party ambient declaration (`@nodesecure/vis-network`'s `utils.d.ts`). + * + * @param {string} [lang] + * @returns {Record>} + */ +export function getI18n(lang = currentLang()) { + return /** @type {Record>} */ ( + window.i18n[lang] + ); +} + +/** + * Returns the application settings config. + * + * Works around `window.settings`'s declared type being unreliably shadowed by a + * conflicting third-party ambient declaration (`@nodesecure/vis-network`'s + * `dataset.d.ts` declares `Window.settings: { config: { showFriendlyDependencies } }`). + * + * @returns {import("../types.js").AppConfig} + */ +export function getSettingsConfig() { + return /** @type {import("../types.js").AppConfig} */ (/** @type {unknown} */ ( + window.settings.config + )); +} + +/** + * `window.navigation` collides with the browser's native, read-only Navigation API. + * The app intentionally shadows it with its own `ViewNavigation` controller. + * + * @returns {import("../components/navigation/navigation.js").ViewNavigation} + */ +export function getNavigation() { + return /** @type {import("../components/navigation/navigation.js").ViewNavigation} */ (/** @type {unknown} */ ( + window.navigation + )); +} + /** @returns {string} */ export function currentLang() { const detectedLang = document.getElementById("lang")?.dataset.lang; diff --git a/workspaces/cli/public/components/bundlephobia/bundlephobia.js b/workspaces/cli/public/components/bundlephobia/bundlephobia.js index 95a8a8f1..5d8e9059 100644 --- a/workspaces/cli/public/components/bundlephobia/bundlephobia.js +++ b/workspaces/cli/public/components/bundlephobia/bundlephobia.js @@ -84,11 +84,21 @@ class Bundlephobia extends LitElement { version: { type: String } }; + constructor() { + super(); + /** @type {string} */ + this.name = ""; + /** @type {string} */ + this.version = ""; + } + #bunldeTask = new Task(this, { task: async([name, version]) => { const { gzip, size, dependencySizes - } = await getJSON(`/bundle/${this.#httpName(name)}/${version}`); + } = /** @type {{ gzip: number, size: number, dependencySizes: { approximateSize: number }[] }} */ ( + await getJSON(`/bundle/${this.#httpName(name)}/${version}`) + ); const fullSize = dependencySizes.reduce((prev, curr) => prev + curr.approximateSize, 0); return { @@ -100,6 +110,9 @@ class Bundlephobia extends LitElement { args: () => [this.name, this.version] }); + /** + * @param {string} name + */ #httpName(name) { return name.replaceAll("/", "%2F"); } diff --git a/workspaces/cli/public/components/command-palette/command-palette-panels.js b/workspaces/cli/public/components/command-palette/command-palette-panels.js index b2003d6f..439eff3a 100644 --- a/workspaces/cli/public/components/command-palette/command-palette-panels.js +++ b/workspaces/cli/public/components/command-palette/command-palette-panels.js @@ -4,7 +4,7 @@ import { repeat } from "lit/directives/repeat.js"; import { classMap } from "lit/directives/class-map.js"; // Import Internal Dependencies -import { currentLang } from "../../common/utils.js"; +import * as utils from "../../common/utils.js"; import { FLAG_LIST, SIZE_PRESETS, @@ -23,10 +23,29 @@ const kListTitleKeys = { }; /** - * @param {{ linker: Map, queries: Array, inputValue: string, onAdd: Function, onRemove: Function }} props + * @returns {Record} + */ +function getSearchCommandI18n() { + return /** @type {Record} */ (/** @type {unknown} */ (utils.getI18n().search_command)); +} + +/** + * @typedef {import("@nodesecure/vis-network").LinkerEntry} LinkerEntry + * @typedef {{ filter: string, value: string }} SearchQuery + * @typedef {{ display: string, value: string, hint?: string }} HelperValue + */ + +/** + * @param {{ + * linker: Map, + * queries: SearchQuery[], + * inputValue: string, + * onAdd: (filter: string, value: string) => void, + * onRemove: (filter: string, value: string) => void + * }} props */ export function renderFlagPanel({ linker, queries, inputValue, onAdd, onRemove }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); const flagCounts = getFlagCounts(linker); const activeFlags = new Set( queries @@ -65,10 +84,10 @@ export function renderFlagPanel({ linker, queries, inputValue, onAdd, onRemove } } /** - * @param {{ activeFilter: string, onAdd: Function }} props + * @param {{ activeFilter: string, onAdd: (filter: string, value: string) => void }} props */ export function renderRangePanel({ activeFilter, onAdd }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); const isSizeFilter = activeFilter === "size"; const presets = isSizeFilter ? SIZE_PRESETS : VERSION_PRESETS; const title = isSizeFilter ? i18n.section_size : i18n.section_version; @@ -93,12 +112,19 @@ export function renderRangePanel({ activeFilter, onAdd }) { } /** - * @param {{ linker: Map, activeFilter: string, helpers: Array, selectedIndex: number, onAdd: Function }} props + * @param {{ + * linker: Map, + * activeFilter: string, + * helpers: HelperValue[], + * selectedIndex: number, + * onAdd: (filter: string, value: string) => void + * }} props */ export function renderListPanel({ linker, activeFilter, helpers, selectedIndex, onAdd }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); const counts = getFilterValueCounts(linker, activeFilter); - const title = i18n[kListTitleKeys[activeFilter]] ?? activeFilter; + const titleI18nKey = /** @type {Record} */ (kListTitleKeys)[activeFilter]; + const title = i18n[titleI18nKey] ?? activeFilter; return html`
@@ -117,10 +143,10 @@ export function renderListPanel({ linker, activeFilter, helpers, selectedIndex, } /** - * @param {{ helpers: Array, selectedIndex: number, onSelect: Function }} props + * @param {{ helpers: HelperValue[], selectedIndex: number, onSelect: (helper: HelperValue) => void }} props */ export function renderFilterList({ helpers, selectedIndex, onSelect }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); return html`
@@ -138,10 +164,13 @@ export function renderFilterList({ helpers, selectedIndex, onSelect }) { } /** - * @param {{ presets: Array, onApply: Function }} props + * @param {{ + * presets: { id: string, filter: string, value: string }[], + * onApply: (preset: { id: string, filter: string, value: string }) => void + * }} props */ export function renderPresets({ presets, onApply }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); return html`
@@ -161,10 +190,13 @@ export function renderPresets({ presets, onApply }) { } /** - * @param {{ actions: Array<{ id: string, label: string, kbd: string|null }>, onExecute: Function }} props + * @param {{ + * actions: { id: string, label: string, kbd: string|null }[], + * onExecute: (action: { id: string, label: string, kbd: string|null }) => void + * }} props */ export function renderActions({ actions, onExecute }) { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); return html`
@@ -214,14 +246,19 @@ export function renderIgnorePanel({ title, items, ignored, onToggle }) { } /** - * @param {{ results: Array, selectedIndex: number, helperCount: number, onFocus: Function }} props + * @param {{ + * results: { id: string, flags: string, name: string, version: string }[], + * selectedIndex: number, + * helperCount: number, + * onFocus: (id: string) => void + * }} props */ export function renderResults({ results, selectedIndex, helperCount, onFocus }) { if (results.length === 0) { return nothing; } - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); return html`
diff --git a/workspaces/cli/public/components/command-palette/command-palette.js b/workspaces/cli/public/components/command-palette/command-palette.js index fa549eef..db668c0f 100644 --- a/workspaces/cli/public/components/command-palette/command-palette.js +++ b/workspaces/cli/public/components/command-palette/command-palette.js @@ -4,7 +4,7 @@ import { repeat } from "lit/directives/repeat.js"; import { warnings } from "@nodesecure/js-x-ray/warnings"; // Import Internal Dependencies -import { currentLang, vec2Distance } from "../../common/utils.js"; +import * as utils from "../../common/utils.js"; import { EVENTS } from "../../core/events.js"; import { FILTERS_NAME, @@ -42,6 +42,9 @@ const kWarningItems = Object.keys(warnings) return { value: id, label: id.replaceAll("-", " ") }; }); +/** + * @param {string | undefined} shortcut + */ function resolveKbd(shortcut) { if (!shortcut) { return null; @@ -52,9 +55,35 @@ function resolveKbd(shortcut) { return navigator.userAgent.includes("Mac") ? `⌥${key}` : `Alt+${key}`; } -class CommandPalette extends LitElement { +/** + * @returns {Record} + */ +function getSearchCommandI18n() { + return /** @type {Record} */ (/** @type {unknown} */ (utils.getI18n().search_command)); +} + +/** + * @typedef {Object} CommandPackage + * @property {string} id + * @property {string} name + * @property {string} version + * @property {string} flags + * @property {boolean} isHighlighted + */ + +/** + * @typedef {Object} SearchQuery + * @property {string} filter + * @property {string} value + * @property {Set} [matchingIds] + */ + +export class CommandPalette extends LitElement { + /** @type {Map | null} */ #linker = null; + /** @type {import("@nodesecure/vis-network").NodeSecureNetwork | null} */ #network = null; + /** @type {CommandPackage[]} */ #packages = []; static styles = commandPaletteStyles; @@ -68,7 +97,7 @@ class CommandPalette extends LitElement { results: { type: Array } }; - #handleKeydown = (event) => { + #handleKeydown = (/** @type {KeyboardEvent} */ event) => { if ((event.ctrlKey || event.metaKey) && event.key === "k") { event.preventDefault(); if (this.open) { @@ -96,7 +125,12 @@ class CommandPalette extends LitElement { } }; - #init = ({ detail: { linker, packages, network } }) => { + #init = (/** @type {Event} */ event) => { + const { linker, packages, network } = /** @type {CustomEvent<{ + linker: Map, + packages: import("@nodesecure/vis-network").PackageInfo[], + network: import("@nodesecure/vis-network").NodeSecureNetwork + }>} */ (event).detail; this.#linker = linker; this.#network = network; this.#packages = packages.map(({ id, name, version, flags, isHighlighted }) => { @@ -114,9 +148,12 @@ class CommandPalette extends LitElement { super(); this.open = false; this.inputValue = ""; + /** @type {string | null} */ this.activeFilter = null; + /** @type {SearchQuery[]} */ this.queries = []; this.selectedIndex = -1; + /** @type {CommandPackage[]} */ this.results = []; } @@ -140,17 +177,20 @@ class CommandPalette extends LitElement { super.disconnectedCallback(); } + /** + * @param {import("lit").PropertyValues} changedProperties + */ updated(changedProperties) { if (changedProperties.has("open") && this.open) { - this.shadowRoot.querySelector("#cmd-input")?.focus(); + /** @type {HTMLElement | null} */ (this.shadowRoot?.querySelector("#cmd-input"))?.focus(); } if (changedProperties.has("selectedIndex") && this.selectedIndex >= 0) { - this.shadowRoot.querySelector(".selected")?.scrollIntoView({ block: "nearest" }); + this.shadowRoot?.querySelector(".selected")?.scrollIntoView({ block: "nearest" }); } } #isNetworkViewActive() { - return document.getElementById("network--view").classList.contains("hidden") === false; + return /** @type {HTMLElement} */ (document.getElementById("network--view")).classList.contains("hidden") === false; } #openModal() { @@ -170,22 +210,40 @@ class CommandPalette extends LitElement { this.results = []; } + /** + * @returns {Map} + */ + #getLinker() { + return /** @type {Map} */ (this.#linker); + } + #getCurrentMatchingIds() { if (this.queries.length === 0) { return null; } + /** @type {Set | null} */ let ids = null; for (const { filter, value } of this.queries) { - const matches = computeMatches(this.#linker, filter, value); - ids = ids === null ? matches : new Set([...ids].filter((id) => matches.has(id))); + const matches = computeMatches(this.#getLinker(), filter, value); + if (ids === null) { + ids = matches; + } + else { + const previousIds = /** @type {Set} */ (ids); + ids = new Set([...previousIds].filter((id) => matches.has(id))); + } } return ids; } + /** + * @param {string} filter + * @param {string} text + */ #computeLiveResults(filter, text) { - const freshMatches = computeMatches(this.#linker, filter, text); + const freshMatches = computeMatches(this.#getLinker(), filter, text); const constraint = this.#getCurrentMatchingIds(); const matchingIds = constraint === null ? freshMatches @@ -194,8 +252,12 @@ class CommandPalette extends LitElement { this.results = this.#packages.filter((pkg) => matchingIds.has(pkg.id)); } + /** + * @param {string} filter + * @param {string} value + */ #addQuery(filter, value) { - const freshMatches = computeMatches(this.#linker, filter, value); + const freshMatches = computeMatches(this.#getLinker(), filter, value); const constraint = this.#getCurrentMatchingIds(); const matchingIds = constraint === null ? freshMatches @@ -211,6 +273,9 @@ class CommandPalette extends LitElement { } } + /** + * @param {SearchQuery} query + */ #removeQuery(query) { this.queries = this.queries.filter((existing) => existing !== query); const constraint = this.#getCurrentMatchingIds(); @@ -219,6 +284,10 @@ class CommandPalette extends LitElement { : this.#packages.filter((pkg) => constraint.has(pkg.id)); } + /** + * @param {string} filter + * @param {string} value + */ #removeQueryByValue(filter, value) { this.queries = this.queries.filter((query) => !(query.filter === filter && query.value === value)); const constraint = this.#getCurrentMatchingIds(); @@ -243,12 +312,15 @@ class CommandPalette extends LitElement { } this.updateComplete.then(() => { - this.shadowRoot.querySelector("#cmd-input")?.focus(); + /** @type {HTMLElement | null} */ (this.shadowRoot?.querySelector("#cmd-input"))?.focus(); }); } + /** + * @param {Event} event + */ #onInput(event) { - const value = event.target.value; + const value = /** @type {HTMLInputElement} */ (event.target).value; this.inputValue = value; this.selectedIndex = -1; @@ -282,6 +354,9 @@ class CommandPalette extends LitElement { } } + /** + * @param {KeyboardEvent} event + */ #onKeydown(event) { const helpers = this.#visibleHelpers; const total = helpers.length + this.results.length; @@ -316,6 +391,9 @@ class CommandPalette extends LitElement { } } + /** + * @param {number} index + */ #selectByIndex(index) { const helpers = this.#visibleHelpers; if (index < helpers.length) { @@ -327,6 +405,9 @@ class CommandPalette extends LitElement { } } + /** + * @param {{ value: string, display: string, hint?: string, type: string }} helper + */ #selectHelper(helper) { if (helper.type === "filter") { if (FILTER_INSTANT_CONFIRM.has(helper.value)) { @@ -340,11 +421,11 @@ class CommandPalette extends LitElement { } } else { - this.#addQuery(this.activeFilter, helper.value); + this.#addQuery(/** @type {string} */ (this.activeFilter), helper.value); } this.updateComplete.then(() => { - this.shadowRoot.querySelector("#cmd-input")?.focus(); + /** @type {HTMLElement | null} */ (this.shadowRoot?.querySelector("#cmd-input"))?.focus(); }); } @@ -369,17 +450,30 @@ class CommandPalette extends LitElement { } } + /** + * @param {string} id + */ #focusPackage(id) { - this.#network.focusNodeById(id); - window.navigation.setNavByName("network--view"); + this.#getNetwork().focusNodeById(Number(id)); + utils.getNavigation().setNavByName("network--view"); this.#close(); } + /** + * @returns {import("@nodesecure/vis-network").NodeSecureNetwork} + */ + #getNetwork() { + return /** @type {import("@nodesecure/vis-network").NodeSecureNetwork} */ (this.#network); + } + + /** + * @param {{ id: string, shortcut?: string }} action + */ async #executeAction(action) { switch (action.id) { case "toggle_theme": { - const nextTheme = window.settings.config.theme === "dark" ? "light" : "dark"; - const config = window.settings.config; + const nextTheme = utils.getSettingsConfig().theme === "dark" ? "light" : "dark"; + const config = utils.getSettingsConfig(); fetch("/config", { method: "put", body: JSON.stringify({ @@ -399,8 +493,8 @@ class CommandPalette extends LitElement { break; } case "reset_view": - this.#network.network.emit("click", { nodes: [], edges: [] }); - this.#network.network.focus(0, { + /** @type {any} */ (this.#getNetwork().network).emit("click", { nodes: [], edges: [] }); + this.#getNetwork().network.focus(0, { animation: true, scale: 0.35, offset: { x: 150, y: 0 } @@ -441,8 +535,12 @@ class CommandPalette extends LitElement { this.#close(); } + /** + * @param {"flags" | "warnings"} type + * @param {string} value + */ #toggleIgnore(type, value) { - const ignoreSet = window.settings.config.ignore[type]; + const ignoreSet = utils.getSettingsConfig().ignore[type]; if (ignoreSet.has(value)) { ignoreSet.delete(value); } @@ -450,7 +548,7 @@ class CommandPalette extends LitElement { ignoreSet.add(value); } - const config = window.settings.config; + const config = utils.getSettingsConfig(); fetch("/config", { method: "put", body: JSON.stringify({ @@ -477,7 +575,7 @@ class CommandPalette extends LitElement { } #getEmptyQueryMessage() { - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); if (this.queries.length === 1) { const { filter, value } = this.queries[0]; const preset = PRESETS.find((preset) => preset.filter === filter && preset.value === value); @@ -489,33 +587,41 @@ class CommandPalette extends LitElement { return i18n.empty_after_filter; } + /** + * @param {number[]} nodeIds + */ #focusMultiplePackages(nodeIds) { - window.navigation.setNavByName("network--view"); - this.#network.highlightMultipleNodes(nodeIds); - window.locker.lock(); + utils.getNavigation().setNavByName("network--view"); + this.#getNetwork().highlightMultipleNodes(nodeIds); + /** @type {import("../locker/locker.js").Locker} */ (window.locker).lock(); - const currentSelectedNode = window.networkNav.currentNodeParams; + const currentSelectedNode = window.networkNav?.currentNodeParams; const shouldMove = !currentSelectedNode || !nodeIds.includes(currentSelectedNode.nodes[0]); if (shouldMove) { - const origin = this.#network.network.getViewPosition(); + const origin = this.#getNetwork().network.getViewPosition(); const closestNode = nodeIds .map((id) => { - return { id, pos: this.#network.network.getPosition(id) }; + return { id, pos: this.#getNetwork().network.getPosition(id) }; }) - .reduce((nodeA, nodeB) => (vec2Distance(origin, nodeA.pos) < vec2Distance(origin, nodeB.pos) ? nodeA : nodeB)); + .reduce((nodeA, nodeB) => ( + utils.vec2Distance(origin, nodeA.pos) < utils.vec2Distance(origin, nodeB.pos) ? nodeA : nodeB + )); const scale = nodeIds.length > 3 ? 0.25 : 0.35; - this.#network.network.focus(closestNode.id, { animation: true, scale }); + this.#getNetwork().network.focus(closestNode.id, { animation: true, scale }); } this.#close(); } + /** + * @returns {{ value: string, display: string, hint?: string, type: string }[]} + */ get #visibleHelpers() { if (this.activeFilter === null) { const text = this.inputValue.toLowerCase(); - const filterHints = window.i18n[currentLang()].search_command.filter_hints; + const filterHints = getSearchCommandI18n().filter_hints; return [...FILTERS_NAME] .filter((filterName) => text === "" || filterName.startsWith(text)) @@ -529,7 +635,7 @@ class CommandPalette extends LitElement { } const searchText = this.inputValue.slice(this.activeFilter.length + 1).toLowerCase(); - const allValues = getHelperValues(this.#linker, this.activeFilter); + const allValues = getHelperValues(this.#getLinker(), this.activeFilter); return allValues .filter((helper) => searchText === "" || helper.display.toLowerCase().includes(searchText)) @@ -538,15 +644,18 @@ class CommandPalette extends LitElement { }); } + /** + * @param {{ value: string, display: string, hint?: string, type: string }[]} helpers + */ #renderActiveFilterPanel(helpers) { const panelProps = { - linker: this.#linker, + linker: this.#getLinker(), queries: this.queries, inputValue: this.inputValue, - activeFilter: this.activeFilter, + activeFilter: /** @type {string} */ (this.activeFilter), selectedIndex: this.selectedIndex, - onAdd: (filter, value) => this.#addQuery(filter, value), - onRemove: (filter, value) => this.#removeQueryByValue(filter, value) + onAdd: (/** @type {string} */ filter, /** @type {string} */ value) => this.#addQuery(filter, value), + onRemove: (/** @type {string} */ filter, /** @type {string} */ value) => this.#removeQueryByValue(filter, value) }; switch (this.activeFilter) { @@ -561,8 +670,10 @@ class CommandPalette extends LitElement { } #resolveActions() { - const i18n = window.i18n[currentLang()].search_command; - const currentTheme = window.settings?.config?.theme ?? "light"; + const i18n = getSearchCommandI18n(); + const currentTheme = /** @type {import("../../types.js").AppConfig | undefined} */ ( + /** @type {unknown} */ (window.settings?.config) + )?.theme ?? "light"; const targetTheme = currentTheme === "dark" ? "light" : "dark"; const copyCount = this.results.length > 0 ? this.results.length : this.#packages.length; @@ -597,7 +708,7 @@ class CommandPalette extends LitElement { return nothing; } - const i18n = window.i18n[currentLang()].search_command; + const i18n = getSearchCommandI18n(); const helpers = this.#visibleHelpers; const isPanelMode = this.activeFilter !== null; const isEmpty = helpers.length === 0 && this.results.length === 0 && this.inputValue.length > 0; @@ -608,7 +719,9 @@ class CommandPalette extends LitElement { ? renderFilterList({ helpers, selectedIndex: this.selectedIndex, - onSelect: (helper) => this.#selectHelper(helper) + onSelect: (helper) => this.#selectHelper(/** @type {{ value: string, display: string, hint?: string, type: string }} */ ( + helper + )) }) : nothing; @@ -619,7 +732,7 @@ class CommandPalette extends LitElement { role="dialog" aria-modal="true" aria-label="Package search" - @click=${(event) => event.stopPropagation()} + @click=${(/** @type {Event} */ event) => event.stopPropagation()} >
@@ -675,14 +788,14 @@ class CommandPalette extends LitElement { ${showRichPlaceholder ? renderIgnorePanel({ title: i18n.section_ignore_flags, items: FLAG_IGNORE_ITEMS, - ignored: window.settings?.config?.ignore?.flags ?? new Set(), - onToggle: (value) => this.#toggleIgnore("flags", value) + ignored: utils.getSettingsConfig()?.ignore?.flags ?? new Set(), + onToggle: (/** @type {string} */ value) => this.#toggleIgnore("flags", value) }) : nothing} ${showRichPlaceholder ? renderIgnorePanel({ title: i18n.section_ignore_warnings, items: kWarningItems, - ignored: window.settings?.config?.ignore?.warnings ?? new Set(), - onToggle: (value) => this.#toggleIgnore("warnings", value) + ignored: utils.getSettingsConfig()?.ignore?.warnings ?? new Set(), + onToggle: (/** @type {string} */ value) => this.#toggleIgnore("warnings", value) }) : nothing} ${renderResults({ results: this.results, diff --git a/workspaces/cli/public/components/command-palette/filters.js b/workspaces/cli/public/components/command-palette/filters.js index abc2a9cb..eddd5986 100644 --- a/workspaces/cli/public/components/command-palette/filters.js +++ b/workspaces/cli/public/components/command-palette/filters.js @@ -49,7 +49,7 @@ export const FILTER_INSTANT_CONFIRM = new Set(["highlighted"]); /** * Returns per-flag package counts across the full linker. * - * @param {Map} linker + * @param {Map} linker * @returns {Map} */ export function getFlagCounts(linker) { @@ -66,7 +66,7 @@ export function getFlagCounts(linker) { /** * Returns per-value package counts for list-type filters. * - * @param {Map} linker + * @param {Map} linker * @param {string} filterName * @returns {Map} */ @@ -93,12 +93,16 @@ export function getFilterValueCounts(linker, filterName) { return counts; } +/** + * @param {import("@nodesecure/vis-network").LinkerEntry} opt + * @param {string} filterName + */ function getValuesForCount(opt, filterName) { switch (filterName) { case "license": return opt.uniqueLicenseIds ?? []; case "ext": - return opt.composition.extensions.filter((ext) => ext !== ""); + return opt.composition.extensions.filter((/** @type {string} */ ext) => ext !== ""); case "builtin": return opt.composition.required_nodejs; case "author": { @@ -115,7 +119,7 @@ function getValuesForCount(opt, filterName) { } /** - * @param {Map} linker + * @param {Map} linker * @param {string} filterName * @returns {{ display: string, value: string }[]} */ @@ -196,7 +200,7 @@ export function getHelperValues(linker, filterName) { /** * Returns the Set of package IDs (as strings) matching the given filter+value. * - * @param {Map} linker + * @param {Map} linker * @param {string} filterName * @param {string} inputValue * @returns {Set} @@ -220,7 +224,7 @@ export function computeMatches(linker, filterName, inputValue) { /** * Collect packages that depend on package matching inputValue * - * @param {Map} linker + * @param {Map} linker * @param {string} inputValue * @returns {Set} */ @@ -252,6 +256,11 @@ function computeDepMatches(linker, inputValue) { return matchingIds; } +/** + * @param {import("@nodesecure/vis-network").LinkerEntry} opt + * @param {string} filterName + * @param {string} inputValue + */ function matchesFilter(opt, filterName, inputValue) { switch (filterName) { case "package": { diff --git a/workspaces/cli/public/components/command-palette/search-chip.js b/workspaces/cli/public/components/command-palette/search-chip.js index c1a748e3..f9594b26 100644 --- a/workspaces/cli/public/components/command-palette/search-chip.js +++ b/workspaces/cli/public/components/command-palette/search-chip.js @@ -58,6 +58,14 @@ class SearchChip extends LitElement { value: { type: String } }; + constructor() { + super(); + /** @type {string} */ + this.filter = ""; + /** @type {string} */ + this.value = ""; + } + #onRemove = () => { this.dispatchEvent(new CustomEvent("remove", { bubbles: true, composed: true })); }; diff --git a/workspaces/cli/public/components/expandable/expandable.js b/workspaces/cli/public/components/expandable/expandable.js index e170fe87..4a945036 100644 --- a/workspaces/cli/public/components/expandable/expandable.js +++ b/workspaces/cli/public/components/expandable/expandable.js @@ -3,13 +3,9 @@ import { LitElement, html, css } from "lit"; import { when } from "lit/directives/when.js"; // Import Internal Dependencies -import { currentLang } from "../../common/utils"; +import { getI18n } from "../../common/utils"; import "../icon/icon.js"; -/** - * @typedef {Record} I18nLanguage - */ - /** * "Expandable" web component displaying a toggle button with an icon. * @element expandable-span @@ -58,19 +54,15 @@ span.expandable nsecure-icon { } render() { - const lang = currentLang(); - const i18n = - /** @type I18nLanguage */ - (window.i18n); - const translations = i18n[lang].home; + const i18n = getI18n(); return html` `; diff --git a/workspaces/cli/public/components/file-box/file-box.js b/workspaces/cli/public/components/file-box/file-box.js index b80effdb..2be841a3 100644 --- a/workspaces/cli/public/components/file-box/file-box.js +++ b/workspaces/cli/public/components/file-box/file-box.js @@ -5,7 +5,7 @@ import { when } from "lit/directives/when.js"; // Import Internal Dependencies import "../icon/icon.js"; -class FileBox extends LitElement { +export class FileBox extends LitElement { static styles = css` .box-file-info { display: flex; @@ -103,8 +103,11 @@ class FileBox extends LitElement { super(); this.title = ""; this.fileName = ""; + /** @type {string | null} */ this.titleHref = "#"; + /** @type {string | null} */ this.fileHref = null; + /** @type {string | null} */ this.severity = null; } diff --git a/workspaces/cli/public/components/gauge/gauge.js b/workspaces/cli/public/components/gauge/gauge.js index f3ddc259..56acf071 100644 --- a/workspaces/cli/public/components/gauge/gauge.js +++ b/workspaces/cli/public/components/gauge/gauge.js @@ -7,7 +7,15 @@ import { when } from "lit/directives/when.js"; import { EVENTS } from "../../core/events.js"; import "../expandable/expandable.js"; -class Gauge extends LitElement { +/** + * @typedef {Object} GaugeItem + * @property {string} name + * @property {number} value + * @property {string | null} [link] + * @property {string[] | null} [chips] + */ + +export class Gauge extends LitElement { static styles = css` .gauge { display: flex; @@ -110,10 +118,14 @@ class Gauge extends LitElement { constructor() { super(); + /** @type {GaugeItem[]} */ this.data = []; this.maxLength = 8; this.isClosed = true; - this.settingsChanged = ({ detail: { theme } }) => { + /** @type {string} */ + this.theme = ""; + this.settingsChanged = (/** @type {Event} */ event) => { + const { theme } = /** @type {CustomEvent<{ theme: string }>} */ (event).detail; if (theme !== this.theme) { this.theme = theme; } @@ -147,6 +159,9 @@ class Gauge extends LitElement {
`; } + /** + * @param {{ text: string, value: number, chips: string[] | null, link: string | null, length: number }} params + */ #createLine({ text, value, chips, link, length }) { @@ -157,7 +172,7 @@ class Gauge extends LitElement { ${value}
${when(chips, - () => html`
${this.#createChips(chips)}
`, + () => html`
${this.#createChips(/** @type {string[]} */ (chips))}
`, () => nothing)} `; @@ -165,7 +180,7 @@ class Gauge extends LitElement { ${when(link !== null, () => html`
{ - window.open(link, "_blank"); + window.open(/** @type {string} */ (link), "_blank"); }}> ${lineColumn}
@@ -178,6 +193,9 @@ class Gauge extends LitElement { `; } + /** + * @param {string[]} chips + */ #createChips(chips) { return html` ${repeat(chips, @@ -186,6 +204,9 @@ class Gauge extends LitElement { `; } + /** + * @param {number} percent + */ #createGaugeBar(percent) { return html`
@@ -193,6 +214,10 @@ class Gauge extends LitElement {
`; } + /** + * @param {number} value + * @param {number} length + */ #pourcentFromValue(value, length) { return Math.round((value / length) * 100); } diff --git a/workspaces/cli/public/components/icon/icon.js b/workspaces/cli/public/components/icon/icon.js index 1e0d7183..b3ebbdcc 100644 --- a/workspaces/cli/public/components/icon/icon.js +++ b/workspaces/cli/public/components/icon/icon.js @@ -199,12 +199,18 @@ export class Icon extends LitElement { name: { type: String } }; + constructor() { + super(); + /** @type {string} */ + this.name = ""; + } + render() { if (!(this.name in kIcons)) { return nothing; } - return kIcons[this.name]; + return kIcons[/** @type {keyof typeof kIcons} */ (this.name)]; } } diff --git a/workspaces/cli/public/components/items-list/items-list.js b/workspaces/cli/public/components/items-list/items-list.js index d8e1289f..5d3c59e1 100644 --- a/workspaces/cli/public/components/items-list/items-list.js +++ b/workspaces/cli/public/components/items-list/items-list.js @@ -63,9 +63,11 @@ justify-content: start; constructor() { super(); + /** @type {string[]} */ this.items = []; this.isClosed = true; this.itemsToShowLength = 5; + /** @type {((item: string) => void) | null} */ this.onClickItem = null; this.variant = "column"; this.shouldShowEveryItems = false; @@ -85,7 +87,7 @@ justify-content: start; (item) => item, (item) => (typeof this.onClickItem === "function" ? html`
  • { - this.onClickItem(item); + /** @type {(item: string) => void} */ (this.onClickItem)(item); }}>${item}
  • ` : html`
  • ${item}
  • `)) } diff --git a/workspaces/cli/public/components/items-list/view-model.js b/workspaces/cli/public/components/items-list/view-model.js index 736f804c..da5643f6 100644 --- a/workspaces/cli/public/components/items-list/view-model.js +++ b/workspaces/cli/public/components/items-list/view-model.js @@ -1,3 +1,10 @@ +/** + * @param {Object} params + * @param {string[]} params.items + * @param {boolean} params.isClosed + * @param {number} params.itemsToShowLength + * @param {boolean} [params.shouldShowEveryItems] + */ export function selectVisibleItems({ items, isClosed, itemsToShowLength, shouldShowEveryItems = false }) { diff --git a/workspaces/cli/public/components/legend/legend.js b/workspaces/cli/public/components/legend/legend.js index 45bc28af..e4538600 100644 --- a/workspaces/cli/public/components/legend/legend.js +++ b/workspaces/cli/public/components/legend/legend.js @@ -3,7 +3,7 @@ import { LitElement, html, css, nothing } from "lit"; import { COLORS } from "@nodesecure/vis-network"; // Import Internal Dependencies -import { currentLang } from "../../common/utils.js"; +import { getI18n } from "../../common/utils.js"; class Legend extends LitElement { static properties = { @@ -83,7 +83,7 @@ class Legend extends LitElement { } const colors = COLORS.LIGHT; - const { legend } = window.i18n[currentLang()]; + const { legend } = getI18n(); return html`
    @@ -95,6 +95,10 @@ class Legend extends LitElement { `; } + /** + * @param {typeof COLORS.LIGHT[keyof typeof COLORS.LIGHT]} theme + * @param {string} text + */ #createLegendBoxElement( theme, text diff --git a/workspaces/cli/public/components/locked-navigation/locked-navigation.js b/workspaces/cli/public/components/locked-navigation/locked-navigation.js index 0a15907e..24209d28 100644 --- a/workspaces/cli/public/components/locked-navigation/locked-navigation.js +++ b/workspaces/cli/public/components/locked-navigation/locked-navigation.js @@ -56,6 +56,10 @@ export class LockedNavigation extends LitElement { constructor() { super(); this.isLocked = false; + /** @type {string} */ + this.nextLabel = ""; + /** @type {string} */ + this.prevLabel = ""; this.lock = () => { this.isLocked = true; diff --git a/workspaces/cli/public/components/locker/locker.js b/workspaces/cli/public/components/locker/locker.js index 55a8a327..d5f4be14 100644 --- a/workspaces/cli/public/components/locker/locker.js +++ b/workspaces/cli/public/components/locker/locker.js @@ -81,6 +81,8 @@ export class Locker extends LitElement { this.locked = false; this.unlockAuthorized = true; this.isNetworkViewHidden = false; + /** @type {import("@nodesecure/vis-network").NodeSecureNetwork | undefined} */ + this.nsn = undefined; this.hideNetworkView = () => { if (this.isNetworkViewHidden) { return; @@ -95,14 +97,15 @@ export class Locker extends LitElement { this.isNetworkViewHidden = false; }; - this.onKeyDown = (event) => { - const isTargetInput = event.target.tagName === "INPUT"; - const isTargetPopup = event.target.id === "popup--background"; + this.onKeyDown = (/** @type {KeyboardEvent} */ event) => { + const target = /** @type {HTMLElement} */ (event.target); + const isTargetInput = target.tagName === "INPUT"; + const isTargetPopup = target.id === "popup--background"; if (this.isNetworkViewHidden || isTargetInput || isTargetPopup) { return; } - const hotkeys = JSON.parse(localStorage.getItem("hotkeys")); + const hotkeys = JSON.parse(/** @type {string} */ (localStorage.getItem("hotkeys"))); switch (event.key.toUpperCase()) { case hotkeys.lock: { this.auto(); @@ -126,16 +129,21 @@ export class Locker extends LitElement { super.disconnectedCallback(); } + /** + * @param {import("lit").PropertyValues} changedProperties + */ updated(changedProperties) { if (changedProperties.has("nsn")) { - const oldNsn = changedProperties.get("nsn"); + const oldNsn = /** @type {import("@nodesecure/vis-network").NodeSecureNetwork | undefined} */ ( + changedProperties.get("nsn") + ); if (oldNsn) { - oldNsn.network.off("highlight_done", this.highlightDone); + oldNsn.network.off(/** @type {any} */ ("highlight_done"), this.highlightDone); } if (this.nsn) { - this.nsn.network.on("highlight_done", this.highlightDone); + this.nsn.network.on(/** @type {any} */ ("highlight_done"), this.highlightDone); } } } @@ -152,14 +160,14 @@ export class Locker extends LitElement {
    -

    ${this.locked ? window.i18n[utils.currentLang()].network.locked - : window.i18n[utils.currentLang()].network.unlocked}

    +

    ${this.locked ? utils.getI18n().network.locked + : utils.getI18n().network.unlocked}

    `; } auto() { // Refuse locking if there is no multi selections - if (this.nsn.lastHighlightedIds === null) { + if (!this.nsn || this.nsn.lastHighlightedIds === null) { return; } @@ -198,13 +206,17 @@ export class Locker extends LitElement { window.dispatchEvent(new CustomEvent(EVENTS.UNLOCKED, { composed: true })); // No node selected, so we reset highlight + if (!this.nsn) { + return; + } + const selectedNode = window.networkNav.currentNodeParams; if (selectedNode === null) { this.nsn.resetHighlight(); } else if (this.nsn.lastHighlightedIds !== null) { this.nsn.lastHighlightedIds = null; - this.nsn.neighbourHighlight(selectedNode, window.i18n[utils.currentLang()]); + this.nsn.neighbourHighlight(/** @type {any} */ (selectedNode), /** @type {any} */ (utils.getI18n())); } } } diff --git a/workspaces/cli/public/components/navigation/navigation.js b/workspaces/cli/public/components/navigation/navigation.js index 845c4d4a..0bedf7a2 100644 --- a/workspaces/cli/public/components/navigation/navigation.js +++ b/workspaces/cli/public/components/navigation/navigation.js @@ -16,73 +16,87 @@ export class ViewNavigation { static DefaultActiveMenu = "network--view"; constructor() { + /** @type {HTMLElement | null} */ this.activeMenu = null; + /** @type {Map} */ this.menus = new Map(); const defaultMenu = this.getAnchor(); const navElements = document.querySelectorAll("#view-navigation li"); for (const navigationMenu of navElements) { const menuName = navigationMenu.getAttribute("data-menu"); - this.menus.set(menuName, navigationMenu); + if (!menuName) { + continue; + } + this.menus.set(menuName, /** @type {HTMLElement} */ (navigationMenu)); if (menuName === defaultMenu) { - this.setNewActiveMenu(navigationMenu); + this.setNewActiveMenu(/** @type {HTMLElement} */ (navigationMenu)); } - navigationMenu.addEventListener("click", () => this.onNavigationSelected(navigationMenu)); + navigationMenu.addEventListener("click", () => this.onNavigationSelected(/** @type {HTMLElement} */ (navigationMenu))); } document.addEventListener("keydown", (event) => { - const isWikiOpen = document.getElementById("documentation-root-element").classList.contains("slide-in"); - const isTargetPopup = event.target.id === "popup--background"; + const wikiRoot = /** @type {HTMLElement} */ (document.getElementById("documentation-root-element")); + const isWikiOpen = wikiRoot.classList.contains("slide-in"); + const eventTarget = /** @type {HTMLElement} */ (event.target); + const isTargetPopup = eventTarget.id === "popup--background"; const isPopupOpened = document.querySelector("#popup--background.show"); - const isTargetInput = event.target.tagName === "INPUT"; + const isTargetInput = eventTarget.tagName === "INPUT"; const isSearchCommandOpen = Boolean(document.querySelector("command-palette")?.open); if (isTargetPopup || isWikiOpen || isTargetInput || isPopupOpened || isSearchCommandOpen) { return; } - const hotkeys = JSON.parse(localStorage.getItem("hotkeys")); + const hotkeys = JSON.parse(/** @type {string} */ (localStorage.getItem("hotkeys"))); switch (event.key.toUpperCase()) { case hotkeys.home: { - this.onNavigationSelected(this.menus.get("home--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("home--view"))); break; } case hotkeys.network: { - this.onNavigationSelected(this.menus.get("network--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("network--view"))); break; } case hotkeys.settings: { - this.onNavigationSelected(this.menus.get("settings--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("settings--view"))); break; } case hotkeys.search: { - this.onNavigationSelected(this.menus.get("search--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("search--view"))); break; } case hotkeys.tree: { - this.onNavigationSelected(this.menus.get("tree--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("tree--view"))); break; } case hotkeys.warnings: { - this.onNavigationSelected(this.menus.get("warnings--view")); + this.onNavigationSelected(/** @type {HTMLElement} */ (this.menus.get("warnings--view"))); break; } } }); } + /** + * @param {string} navName + */ setNavByName(navName) { - this.onNavigationSelected(this.menus.get(navName)); + const selectedNav = this.menus.get(navName); + if (!selectedNav) { + return; + } + this.onNavigationSelected(selectedNav); } /** * @param {!HTMLElement} selectedNav */ setNewActiveMenu(selectedNav) { - const menuName = selectedNav.getAttribute("data-menu"); + const menuName = /** @type {string} */ (selectedNav.getAttribute("data-menu")); - document.getElementById(menuName).classList.remove("hidden"); + /** @type {HTMLElement} */ (document.getElementById(menuName)).classList.remove("hidden"); selectedNav.classList.add("active"); this.setAnchor(menuName); @@ -94,10 +108,11 @@ export class ViewNavigation { } disableActiveMenu() { - const menuName = this.activeMenu.getAttribute("data-menu"); - const view = document.getElementById(menuName); + const activeMenu = /** @type {HTMLElement} */ (this.activeMenu); + const menuName = /** @type {string} */ (activeMenu.getAttribute("data-menu")); + const view = /** @type {HTMLElement} */ (document.getElementById(menuName)); - this.activeMenu.classList.remove("active"); + activeMenu.classList.remove("active"); view.classList.add("hidden"); if (menuName === "network--view") { @@ -105,6 +120,9 @@ export class ViewNavigation { } } + /** + * @returns {string} + */ getAnchor() { const documentURL = new URL(document.URL); const anchorName = documentURL.searchParams.get("view") ?? ViewNavigation.DefaultActiveMenu; @@ -112,11 +130,14 @@ export class ViewNavigation { return kAvailableView.has(anchorName) ? anchorName : ViewNavigation.DefaultActiveMenu; } + /** + * @param {string} anchorName + */ setAnchor(anchorName) { const newDocumentURL = new URL(document.URL); newDocumentURL.searchParams.set("view", anchorName); - window.history.replaceState(void 0, void 0, newDocumentURL); + window.history.replaceState(null, "", newDocumentURL); } /** @@ -131,6 +152,9 @@ export class ViewNavigation { } } + /** + * @param {string} menuName + */ hideMenu(menuName) { const menu = this.menus.get(menuName); if (!menu) { @@ -144,6 +168,9 @@ export class ViewNavigation { menu.classList.add("hidden"); } + /** + * @param {string} menuName + */ showMenu(menuName) { const menu = this.menus.get(menuName); if (!menu) { diff --git a/workspaces/cli/public/components/network-breadcrumb/network-breadcrumb.js b/workspaces/cli/public/components/network-breadcrumb/network-breadcrumb.js index 24e48156..f9bf4edb 100644 --- a/workspaces/cli/public/components/network-breadcrumb/network-breadcrumb.js +++ b/workspaces/cli/public/components/network-breadcrumb/network-breadcrumb.js @@ -3,9 +3,22 @@ import { LitElement, html, css, nothing } from "lit"; // Import Internal Dependencies import { EVENTS } from "../../core/events.js"; -import { currentLang } from "../../common/utils.js"; - -class NetworkBreadcrumb extends LitElement { +import { getI18n } from "../../common/utils.js"; + +/** + * @typedef {Object} BreadcrumbEntry + * @property {string} name + * @property {string} version + */ + +/** + * @typedef {Object} BreadcrumbSibling + * @property {number} nodeId + * @property {string} name + * @property {string} version + */ + +export class NetworkBreadcrumb extends LitElement { static styles = css` :host { --breadcrumb-bg: var(--primary); @@ -185,10 +198,15 @@ class NetworkBreadcrumb extends LitElement { constructor() { super(); + /** @type {BreadcrumbEntry | null} */ this.root = null; + /** @type {BreadcrumbEntry[]} */ this.stack = []; + /** @type {BreadcrumbSibling[][]} */ this.siblings = []; + /** @type {import("../../types.js").CachedSpec[]} */ this.packages = []; + /** @type {number | null} */ this._openDropdown = null; this._rootSwitcherOpen = false; } @@ -224,6 +242,9 @@ class NetworkBreadcrumb extends LitElement { })); } + /** + * @param {number} index + */ #handleBack(index) { this.dispatchEvent(new CustomEvent(EVENTS.DRILL_BACK, { detail: { index }, @@ -232,12 +253,21 @@ class NetworkBreadcrumb extends LitElement { })); } + /** + * @param {number} index + * @param {MouseEvent} event + */ #toggleDropdown(index, event) { event.stopPropagation(); this._openDropdown = this._openDropdown === index ? null : index; } + /** + * @param {number} stackIndex + * @param {number} nodeId + * @param {MouseEvent} event + */ #handleSiblingClick(stackIndex, nodeId, event) { event.stopPropagation(); @@ -249,12 +279,19 @@ class NetworkBreadcrumb extends LitElement { })); } + /** + * @param {MouseEvent} event + */ #toggleRootSwitcher(event) { event.stopPropagation(); this._rootSwitcherOpen = !this._rootSwitcherOpen; } + /** + * @param {string} spec + * @param {MouseEvent} event + */ #handleRootSwitch(spec, event) { event.stopPropagation(); @@ -266,6 +303,9 @@ class NetworkBreadcrumb extends LitElement { })); } + /** + * @param {MouseEvent} event + */ #handleRootRemove(event) { event.stopPropagation(); @@ -282,7 +322,7 @@ class NetworkBreadcrumb extends LitElement { const otherPackages = this.packages ?? []; const isInDrill = this.stack.length > 0; - const i18n = window.i18n[currentLang()]; + const i18n = getI18n(); return html` ${otherPackages.length > 0 ? html` @@ -296,7 +336,7 @@ class NetworkBreadcrumb extends LitElement { ${i18n.network.switchPayload} ${otherPackages.map((pkg) => html` - `)} @@ -321,12 +361,14 @@ class NetworkBreadcrumb extends LitElement { ? html` ${this._openDropdown === stackIndex ? html`