diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js index c9dd4208..6f891962 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js @@ -885,7 +885,7 @@ function elementById(id) { if (restoreFocus) more.focus(); }; api.closeMobileOverlays = closeMore; - ["statistics", "recorder", "settings", "about"].forEach((tabName) => { + ["satellites", "statistics", "recorder", "settings", "about"].forEach((tabName) => { const source = nav.querySelector(`[data-tab="${tabName}"]`); if (!source) return; const item = document.createElement("button"); @@ -1529,6 +1529,7 @@ var TAB_ORDER = [ "bookmarks", "digital-modes", "map", + "satellites", "statistics", "recorder", "settings", @@ -1539,6 +1540,7 @@ var TAB_PATHS = { bookmarks: "/bookmarks", "digital-modes": "/digital-modes", map: "/map", + satellites: "/satellites", statistics: "/statistics", recorder: "/recorder", settings: "/settings", @@ -1756,6 +1758,7 @@ var pluginGroups = { ], "map-data": ["/map-core.js", "/ais.js", "/vdes.js", "/aprs.js", "/hf-aprs.js"], map: ["/map-core.js", "/ais.js", "/vdes.js", "/aprs.js", "/hf-aprs.js", "/sat.js", "/sat-scheduler.js"], + satellites: ["/satellite-predictions.js"], statistics: ["/map-core.js"], bookmarks: ["/bookmarks.js"], recorder: [], @@ -5844,6 +5847,7 @@ function _initMapWhenReady() { } function navigateToTab(name, options = {}) { window.trxUi?.closeMobileOverlays?.(); + const leavingSatellites = _activeTab === "satellites" && name !== "satellites"; const { updateHistory = true, replaceHistory = false } = options; if (authEnabled && !authRole && name !== "main") { showAuthGate(false); @@ -5873,7 +5877,10 @@ function navigateToTab(name, options = {}) { updateTabHistory(name, replaceHistory); } scheduleSpectrumLayout(); - void loadPluginsForTab(name).catch((error) => { + if (leavingSatellites) window.clearSatPredictionDom?.(); + void loadPluginsForTab(name).then(() => { + if (name === "satellites") window.refreshSatPredictions?.(); + }).catch((error) => { console.error(error); }); if (name === "map") { @@ -6320,9 +6327,6 @@ function _wireSubTabBar(bar) { if (window.refreshCwTonePicker) window.refreshCwTonePicker(); }); } - if (btn.dataset.subtab !== "sat" && typeof window.clearSatPredictionDom === "function") { - window.clearSatPredictionDom(); - } }); } document.querySelectorAll(".sub-tab-bar").forEach(_wireSubTabBar); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat.js index 44bd3940..0c9ca257 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/sat.js @@ -8,7 +8,6 @@ var satDom = { status: document.getElementById("sat-status"), liveView: document.getElementById("sat-live-view"), historyView: document.getElementById("sat-history-view"), - predictionsView: document.getElementById("sat-predictions-view"), liveLatest: document.getElementById("sat-live-latest"), historyList: document.getElementById("sat-history-list"), historyCount: document.getElementById("sat-history-count"), @@ -17,29 +16,12 @@ var satDom = { typeFilter: document.getElementById("sat-type-filter"), lrptState: document.getElementById("sat-lrpt-state"), viewLiveBtn: document.getElementById("sat-view-live"), - viewHistoryBtn: document.getElementById("sat-view-history"), - viewPredBtn: document.getElementById("sat-view-predictions"), - predFilter: document.getElementById("sat-pred-filter"), - predMinEl: document.getElementById("sat-pred-min-el"), - predCategory: document.getElementById("sat-pred-category"), - predCurrentList: document.getElementById("sat-pred-current-list"), - predUpcomingList: document.getElementById("sat-pred-list"), - predCurrentSec: document.getElementById("sat-pred-current-section"), - predUpcomingSec: document.getElementById("sat-pred-upcoming-section"), - predStatus: document.getElementById("sat-pred-status") + viewHistoryBtn: document.getElementById("sat-view-history") }; var satImageHistory = []; var SAT_MAX_IMAGES = 100; -var SAT_PRED_PAGE_SIZE = 50; -var satPredShowAll = false; var satFilterText = ""; var satActiveView = "live"; -var satPredData = []; -var satPredFilterText = ""; -var satPredMinEl = 0; -var satPredCategory = "all"; -var satPredSatCount = 0; -var satPredCountdownTimer = null; function scheduleSatUi(key, job) { if (typeof satWindow.trxScheduleUiFrameJob === "function") { satWindow.trxScheduleUiFrameJob(key, job); @@ -48,37 +30,19 @@ function scheduleSatUi(key, job) { job(); } function switchSatView(view) { - const leavingPredictions = satActiveView === "predictions" && view !== "predictions"; satActiveView = view; if (satDom.liveView) satDom.liveView.style.display = view === "live" ? "" : "none"; if (satDom.historyView) satDom.historyView.style.display = view === "history" ? "" : "none"; - if (satDom.predictionsView) satDom.predictionsView.style.display = view === "predictions" ? "" : "none"; if (satDom.viewLiveBtn) satDom.viewLiveBtn.classList.toggle("sat-view-active", view === "live"); if (satDom.viewHistoryBtn) satDom.viewHistoryBtn.classList.toggle("sat-view-active", view === "history"); - if (satDom.viewPredBtn) satDom.viewPredBtn.classList.toggle("sat-view-active", view === "predictions"); - if (leavingPredictions) clearPredictionDom(); - if (view === "history") { - renderSatHistoryTable(); - } else if (view === "predictions") { - satPredShowAll = false; - void loadSatPredictions(); - } + if (view === "history") renderSatHistoryTable(); } -function clearPredictionDom() { - stopCountdownTimer(); - if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; - if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; -} -satWindow.clearSatPredictionDom = clearPredictionDom; satDom.viewLiveBtn?.addEventListener("click", () => { switchSatView("live"); }); satDom.viewHistoryBtn?.addEventListener("click", () => { switchSatView("history"); }); -satDom.viewPredBtn?.addEventListener("click", () => { - switchSatView("predictions"); -}); var lastSatLrptOn = null; satWindow.updateSatLiveState = function(update) { if (!satDom.lrptState) return; @@ -275,200 +239,6 @@ document.getElementById("settings-clear-sat-history")?.addEventListener("click", } })(); }); -function azToCardinal(deg) { - const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]; - return dirs[Math.round(deg / 45) % 8] ?? "N"; -} -function formatPredTime(ms) { - const d = new Date(ms); - const now = /* @__PURE__ */ new Date(); - const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const day = d.getUTCDay() !== now.getUTCDay() ? `${dayNames[d.getUTCDay()] ?? ""} ` : ""; - const hh = String(d.getUTCHours()).padStart(2, "0"); - const mm = String(d.getUTCMinutes()).padStart(2, "0"); - return `${day}${hh}:${mm}`; -} -function formatPredDuration(s) { - if (s >= 60) return `${Math.round(s / 60)} min`; - return `${s}s`; -} -function formatCountdown(ms) { - const totalSec = Math.max(0, Math.floor(ms / 1e3)); - const m = Math.floor(totalSec / 60); - const s = totalSec % 60; - return `${m}:${String(s).padStart(2, "0")}`; -} -function elevationClass(deg) { - if (deg >= 45) return "sat-pred-el-high"; - if (deg >= 10) return "sat-pred-el-mid"; - return "sat-pred-el-low"; -} -function stopCountdownTimer() { - if (satPredCountdownTimer) { - clearInterval(satPredCountdownTimer); - satPredCountdownTimer = null; - } -} -function startCountdownTimer(container) { - const countdownEls = container?.querySelectorAll(".sat-pred-col-countdown") ?? []; - if (countdownEls.length === 0) return; - satPredCountdownTimer = setInterval(() => { - if (satActiveView !== "predictions") { - stopCountdownTimer(); - return; - } - const n = Date.now(); - let anyActive = false; - for (const el of countdownEls) { - const los = Number.parseInt(el.dataset.los ?? "0", 10); - const rem = los - n; - if (rem > 0) { - el.textContent = formatCountdown(rem); - anyActive = true; - } else { - el.textContent = "0:00"; - } - } - if (!anyActive) { - stopCountdownTimer(); - renderSatPredictions(getFilteredPredictions()); - } - }, 1e3); -} -function buildCurrentPassRow(pass, now) { - const row = document.createElement("div"); - row.className = "sat-pred-row-current"; - const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; - const remaining = Math.max(0, pass.los_ms - now); - row.innerHTML = [ - `${pass.satellite}`, - `${pass.max_elevation_deg.toFixed(1)}°`, - `${formatPredTime(pass.aos_ms)}`, - `${formatPredTime(pass.los_ms)}`, - `${formatCountdown(remaining)}`, - `${dir}` - ].join(""); - return row; -} -function buildUpcomingPassRow(pass) { - const row = document.createElement("div"); - row.className = "sat-pred-row"; - const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; - row.innerHTML = [ - `${formatPredTime(pass.aos_ms)}`, - `${pass.satellite}`, - `${pass.max_elevation_deg.toFixed(1)}°`, - `${formatPredDuration(pass.duration_s)}`, - `${dir}` - ].join(""); - return row; -} -function getFilteredPredictions() { - let items = satPredData; - if (satPredCategory !== "all") items = items.filter((p) => p.category === satPredCategory); - if (satPredMinEl > 0) items = items.filter((p) => p.max_elevation_deg >= satPredMinEl); - if (satPredFilterText) items = items.filter((p) => p.satellite.toUpperCase().includes(satPredFilterText)); - return items; -} -function applyPredFilters() { - renderSatPredictions(getFilteredPredictions()); -} -var satPredictionFilter = satDom.predFilter; -satPredictionFilter?.addEventListener("input", () => { - satPredFilterText = satPredictionFilter.value.trim().toUpperCase(); - applyPredFilters(); -}); -var satPredictionMinElevation = satDom.predMinEl; -satPredictionMinElevation?.addEventListener("change", () => { - satPredMinEl = Number.parseInt(satPredictionMinElevation.value, 10) || 0; - applyPredFilters(); -}); -var satPredictionCategory = satDom.predCategory; -satPredictionCategory?.addEventListener("change", () => { - satPredCategory = satPredictionCategory.value; - applyPredFilters(); -}); -function renderSatPredictions(passes, error) { - stopCountdownTimer(); - if (error) { - if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; - if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; - if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none"; - if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none"; - if (satDom.predStatus) satDom.predStatus.textContent = error; - return; - } - if (!Array.isArray(passes) || passes.length === 0) { - if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; - if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; - if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none"; - if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none"; - if (satDom.predStatus) satDom.predStatus.textContent = "No passes found in the next 24 hours."; - return; - } - const now = Date.now(); - const current = passes.filter((p) => p.aos_ms <= now && p.los_ms > now); - const upcoming = passes.filter((p) => p.aos_ms > now); - if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = current.length > 0 ? "" : "none"; - if (satDom.predCurrentList) { - if (current.length === 0) { - satDom.predCurrentList.innerHTML = ""; - } else { - const frag = document.createDocumentFragment(); - for (const pass of current) frag.appendChild(buildCurrentPassRow(pass, now)); - satDom.predCurrentList.replaceChildren(frag); - } - } - const upcomingLimit = satPredShowAll ? upcoming.length : SAT_PRED_PAGE_SIZE; - const visibleUpcoming = upcoming.slice(0, upcomingLimit); - const hiddenCount = upcoming.length - visibleUpcoming.length; - if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = upcoming.length > 0 ? "" : "none"; - if (satDom.predUpcomingList) { - const frag = document.createDocumentFragment(); - for (const pass of visibleUpcoming) frag.appendChild(buildUpcomingPassRow(pass)); - if (hiddenCount > 0) { - const moreRow = document.createElement("div"); - moreRow.className = "sat-pred-row"; - moreRow.style.cursor = "pointer"; - moreRow.style.textAlign = "center"; - moreRow.innerHTML = `Show ${hiddenCount} more passes…`; - moreRow.addEventListener("click", () => { - satPredShowAll = true; - renderSatPredictions(getFilteredPredictions()); - }); - frag.appendChild(moreRow); - } - satDom.predUpcomingList.replaceChildren(frag); - } - if (satDom.predStatus) { - let text = `${current.length} active · ${upcoming.length} upcoming · times in UTC`; - if (satPredSatCount > 0) text += ` · ${satPredSatCount} satellites tracked`; - satDom.predStatus.textContent = text; - } - if (current.length > 0 && satActiveView === "predictions") { - startCountdownTimer(satDom.predCurrentList); - } -} -async function loadSatPredictions() { - if (satDom.predStatus) satDom.predStatus.textContent = "Loading predictions…"; - if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; - if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; - try { - const resp = await fetch("/sat_passes"); - if (!resp.ok) throw new Error(`HTTP ${resp.status}`); - const data = await resp.json(); - satPredSatCount = data.satellite_count || 0; - if (data.error) { - satPredData = []; - renderSatPredictions([], data.error); - } else { - satPredData = data.passes || []; - renderSatPredictions(getFilteredPredictions()); - } - } catch (error) { - renderSatPredictions([], `Failed to load predictions: ${error instanceof Error ? error.message : String(error)}`); - } -} satWindow.satShowOnMap = function(south, west, north, east) { if (typeof satWindow.enableMapSourceFilter === "function") { satWindow.enableMapSourceFilter("sat"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/satellite-predictions.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/satellite-predictions.js new file mode 100644 index 00000000..0c497d88 --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/satellite-predictions.js @@ -0,0 +1,228 @@ +// src/plugins/satellite-predictions.ts +var predWindow = window; +var dom = { + page: document.getElementById("tab-satellites"), + filter: document.getElementById("sat-pred-filter"), + minElevation: document.getElementById("sat-pred-min-el"), + category: document.getElementById("sat-pred-category"), + currentList: document.getElementById("sat-pred-current-list"), + upcomingList: document.getElementById("sat-pred-list"), + currentSection: document.getElementById("sat-pred-current-section"), + upcomingSection: document.getElementById("sat-pred-upcoming-section"), + status: document.getElementById("sat-pred-status") +}; +var PAGE_SIZE = 50; +var predShowAll = false; +var predData = []; +var predFilterText = ""; +var predMinEl = 0; +var predCategory = "all"; +var predSatCount = 0; +var predCountdownTimer = null; +function isPageVisible() { + return !!dom.page && dom.page.style.display !== "none"; +} +function azToCardinal(deg) { + const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]; + return dirs[Math.round(deg / 45) % 8] ?? "N"; +} +function formatPredTime(ms) { + const d = new Date(ms); + const now = /* @__PURE__ */ new Date(); + const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const day = d.getUTCDay() !== now.getUTCDay() ? `${dayNames[d.getUTCDay()] ?? ""} ` : ""; + const hh = String(d.getUTCHours()).padStart(2, "0"); + const mm = String(d.getUTCMinutes()).padStart(2, "0"); + return `${day}${hh}:${mm}`; +} +function formatPredDuration(s) { + if (s >= 60) return `${Math.round(s / 60)} min`; + return `${s}s`; +} +function formatCountdown(ms) { + const totalSec = Math.max(0, Math.floor(ms / 1e3)); + const m = Math.floor(totalSec / 60); + const s = totalSec % 60; + return `${m}:${String(s).padStart(2, "0")}`; +} +function elevationClass(deg) { + if (deg >= 45) return "sat-pred-el-high"; + if (deg >= 10) return "sat-pred-el-mid"; + return "sat-pred-el-low"; +} +function stopCountdownTimer() { + if (predCountdownTimer) { + clearInterval(predCountdownTimer); + predCountdownTimer = null; + } +} +function startCountdownTimer(container) { + const countdownEls = container?.querySelectorAll(".sat-pred-col-countdown") ?? []; + if (countdownEls.length === 0) return; + predCountdownTimer = setInterval(() => { + if (!isPageVisible()) { + stopCountdownTimer(); + return; + } + const n = Date.now(); + let anyActive = false; + for (const el of countdownEls) { + const los = Number.parseInt(el.dataset.los ?? "0", 10); + const rem = los - n; + if (rem > 0) { + el.textContent = formatCountdown(rem); + anyActive = true; + } else { + el.textContent = "0:00"; + } + } + if (!anyActive) { + stopCountdownTimer(); + render(filtered()); + } + }, 1e3); +} +function buildCurrentPassRow(pass, now) { + const row = document.createElement("div"); + row.className = "sat-pred-row-current"; + const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; + const remaining = Math.max(0, pass.los_ms - now); + row.innerHTML = [ + `${pass.satellite}`, + `${pass.max_elevation_deg.toFixed(1)}°`, + `${formatPredTime(pass.aos_ms)}`, + `${formatPredTime(pass.los_ms)}`, + `${formatCountdown(remaining)}`, + `${dir}` + ].join(""); + return row; +} +function buildUpcomingPassRow(pass) { + const row = document.createElement("div"); + row.className = "sat-pred-row"; + const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; + row.innerHTML = [ + `${formatPredTime(pass.aos_ms)}`, + `${pass.satellite}`, + `${pass.max_elevation_deg.toFixed(1)}°`, + `${formatPredDuration(pass.duration_s)}`, + `${dir}` + ].join(""); + return row; +} +function filtered() { + let items = predData; + if (predCategory !== "all") items = items.filter((p) => p.category === predCategory); + if (predMinEl > 0) items = items.filter((p) => p.max_elevation_deg >= predMinEl); + if (predFilterText) items = items.filter((p) => p.satellite.toUpperCase().includes(predFilterText)); + return items; +} +function applyFilters() { + render(filtered()); +} +var filterInput = dom.filter; +filterInput?.addEventListener("input", () => { + predFilterText = filterInput.value.trim().toUpperCase(); + applyFilters(); +}); +var minElevationSelect = dom.minElevation; +minElevationSelect?.addEventListener("change", () => { + predMinEl = Number.parseInt(minElevationSelect.value, 10) || 0; + applyFilters(); +}); +var categorySelect = dom.category; +categorySelect?.addEventListener("change", () => { + predCategory = categorySelect.value; + applyFilters(); +}); +function render(passes, error) { + stopCountdownTimer(); + if (error) { + if (dom.currentList) dom.currentList.innerHTML = ""; + if (dom.upcomingList) dom.upcomingList.innerHTML = ""; + if (dom.currentSection) dom.currentSection.style.display = "none"; + if (dom.upcomingSection) dom.upcomingSection.style.display = "none"; + if (dom.status) dom.status.textContent = error; + return; + } + if (!Array.isArray(passes) || passes.length === 0) { + if (dom.currentList) dom.currentList.innerHTML = ""; + if (dom.upcomingList) dom.upcomingList.innerHTML = ""; + if (dom.currentSection) dom.currentSection.style.display = "none"; + if (dom.upcomingSection) dom.upcomingSection.style.display = "none"; + if (dom.status) dom.status.textContent = "No passes found in the next 24 hours."; + return; + } + const now = Date.now(); + const current = passes.filter((p) => p.aos_ms <= now && p.los_ms > now); + const upcoming = passes.filter((p) => p.aos_ms > now); + if (dom.currentSection) dom.currentSection.style.display = current.length > 0 ? "" : "none"; + if (dom.currentList) { + if (current.length === 0) { + dom.currentList.innerHTML = ""; + } else { + const frag = document.createDocumentFragment(); + for (const pass of current) frag.appendChild(buildCurrentPassRow(pass, now)); + dom.currentList.replaceChildren(frag); + } + } + const upcomingLimit = predShowAll ? upcoming.length : PAGE_SIZE; + const visibleUpcoming = upcoming.slice(0, upcomingLimit); + const hiddenCount = upcoming.length - visibleUpcoming.length; + if (dom.upcomingSection) dom.upcomingSection.style.display = upcoming.length > 0 ? "" : "none"; + if (dom.upcomingList) { + const frag = document.createDocumentFragment(); + for (const pass of visibleUpcoming) frag.appendChild(buildUpcomingPassRow(pass)); + if (hiddenCount > 0) { + const moreRow = document.createElement("div"); + moreRow.className = "sat-pred-row"; + moreRow.style.cursor = "pointer"; + moreRow.style.textAlign = "center"; + moreRow.innerHTML = `Show ${hiddenCount} more passes…`; + moreRow.addEventListener("click", () => { + predShowAll = true; + render(filtered()); + }); + frag.appendChild(moreRow); + } + dom.upcomingList.replaceChildren(frag); + } + if (dom.status) { + let text = `${current.length} active · ${upcoming.length} upcoming · times in UTC`; + if (predSatCount > 0) text += ` · ${predSatCount} satellites tracked`; + dom.status.textContent = text; + } + if (current.length > 0 && isPageVisible()) { + startCountdownTimer(dom.currentList); + } +} +async function load() { + if (dom.status) dom.status.textContent = "Loading predictions…"; + if (dom.currentList) dom.currentList.innerHTML = ""; + if (dom.upcomingList) dom.upcomingList.innerHTML = ""; + try { + const resp = await fetch("/sat_passes"); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const data = await resp.json(); + predSatCount = data.satellite_count || 0; + if (data.error) { + predData = []; + render([], data.error); + } else { + predData = data.passes || []; + render(filtered()); + } + } catch (error) { + render([], `Failed to load predictions: ${error instanceof Error ? error.message : String(error)}`); + } +} +function clearPredictionDom() { + stopCountdownTimer(); + if (dom.currentList) dom.currentList.innerHTML = ""; + if (dom.upcomingList) dom.upcomingList.innerHTML = ""; +} +predWindow.clearSatPredictionDom = clearPredictionDom; +predWindow.refreshSatPredictions = function() { + predShowAll = false; + void load(); +}; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 45bc6bc1..ec936b08 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -24,6 +24,7 @@ SPDX-License-Identifier: GPL-2.0-or-later + @@ -63,6 +64,10 @@ SPDX-License-Identifier: GPL-2.0-or-later Map + -
@@ -960,50 +964,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
No images yet
- - + +