1import { Controller } from "@hotwired/stimulus" 2 3// Manages mobile tabbed dashboard layout. 4// Persists selected tab in sessionStorage and triggers chart re-renders 5// when tabs become visible via Bootstrap's shown.bs.tab event. 6export default class extends Controller { 7 static values = { gameId: Number } 8 9 connect() { 10 this.boundHandleTabShown = this.handleTabShown.bind(this) 11 this.boundResizePanes = this.resizeActivePane.bind(this) 12 this.element.addEventListener("shown.bs.tab", this.boundHandleTabShown) 13 window.addEventListener("resize", this.boundResizePanes) 14 this.restoreTab() 15 // Size the active pane â multiple attempts to catch tab transitions and async content loads 16 setTimeout(() => this.resizeActivePane(), 300) 17 setTimeout(() => this.resizeActivePane(), 800) 18 } 19 20 disconnect() { 21 this.element.removeEventListener("shown.bs.tab", this.boundHandleTabShown) 22 window.removeEventListener("resize", this.boundResizePanes) 23 } 24 25 get storageKey() { 26 return `pc-mobile-tab-${this.gameIdValue}` 27 } 28 29 restoreTab() { 30 const savedTab = sessionStorage.getItem(this.storageKey) 31 if (!savedTab) return 32 33 const tab = this.element.querySelector(`[data-bs-target="#${savedTab}"]`) 34 if (tab && !tab.classList.contains("active")) { 35 tab.click() 36 } 37 } 38 39 handleTabShown(event) { 40 const paneId = event.target.getAttribute("data-bs-target")?.replace("#", "") 41 if (paneId) { 42 sessionStorage.setItem(this.storageKey, paneId) 43 } 44 45 // Tab panes are siblings of the nav wrapper, so look up from document 46 const pane = document.getElementById(paneId) 47 if (pane) { 48 // Trigger resize on any Chart.js canvases in the newly visible pane 49 pane.querySelectorAll("canvas").forEach((canvas) => { 50 if (typeof Chart !== "undefined") { 51 const chartInstance = Chart.getChart?.(canvas) 52 if (chartInstance) { 53 chartInstance.resize() 54 } 55 } 56 }) 57 58 // Dispatch a custom event so controllers can handle visibility changes 59 pane.dispatchEvent(new CustomEvent("tab:shown", { bubbles: true })) 60 } 61 62 this.resizeActivePane() 63 // Retry after transition fully settles to catch edge cases 64 setTimeout(() => this.resizeActivePane(), 200) 65 } 66 67 resizeActivePane() { 68 // Only constrain height on mobile â desktop shows all panes simultaneously 69 if (window.innerWidth >= 768) { 70 // Clear any previously set height if window was resized from mobile to desktop 71 this.paneContainer.querySelectorAll(".mobile-tab-pane").forEach(pane => { 72 pane.style.height = "" 73 }) 74 return 75 } 76 77 const activePane = this.paneContainer.querySelector(".mobile-tab-pane.show") 78 if (!activePane) return 79 80 const top = activePane.getBoundingClientRect().top 81 activePane.style.height = `${window.innerHeight - top}px` 82 } 83 84 get paneContainer() { 85 // Tab panes span both the main column and the sidebar column, 86 // so search from the nearest row ancestor that contains both. 87 return this.element.closest(".row") || this.element.parentElement 88 } 89 90}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.