Rewild Your Web
web browser dweb
at main 163 lines 4.1 kB view raw
1// SPDX-License-Identifier: AGPL-3.0-or-later 2 3import { 4 LitElement, 5 html, 6} from "//shared.localhost:8888/third_party/lit/lit-all.min.js"; 7 8export class PanelOverview extends LitElement { 9 static properties = { 10 open: { type: Boolean, reflect: true }, 11 panels: { type: Array }, 12 rootWidth: { type: Number }, 13 rootHeight: { type: Number }, 14 }; 15 16 constructor() { 17 super(); 18 this.open = false; 19 this.panels = []; 20 this.rootWidth = 0; 21 this.rootHeight = 0; 22 this.boundKeyHandler = this.handleKeyDown.bind(this); 23 } 24 25 connectedCallback() { 26 super.connectedCallback(); 27 document.addEventListener("keydown", this.boundKeyHandler); 28 } 29 30 disconnectedCallback() { 31 super.disconnectedCallback(); 32 document.removeEventListener("keydown", this.boundKeyHandler); 33 } 34 35 handleKeyDown(e) { 36 if (this.open && e.key === "Escape") { 37 this.close(); 38 } 39 } 40 41 handleContainerClick(e) { 42 // Close if clicking on the background container (not a thumbnail) 43 if (e.target === e.currentTarget) { 44 this.close(); 45 } 46 } 47 48 handleThumbnailClick(e, thumb) { 49 e.stopPropagation(); 50 this.dispatchEvent( 51 new CustomEvent("overview-select", { 52 bubbles: true, 53 composed: true, 54 detail: { 55 webviewId: thumb.webviewId, 56 panelIndex: thumb.panelIndex, 57 }, 58 }), 59 ); 60 this.close(); 61 } 62 63 close() { 64 this.open = false; 65 this.dispatchEvent( 66 new CustomEvent("overview-close", { 67 bubbles: true, 68 composed: true, 69 }), 70 ); 71 } 72 73 // Calculate scale factor to fit all panels 74 calculateScale() { 75 if (!this.panels.length || !this.rootWidth || !this.rootHeight) { 76 return 1; 77 } 78 79 const gap = 16; 80 const availableWidth = this.rootWidth * 0.9; 81 const availableHeight = this.rootHeight * 0.8; 82 83 let totalUnscaledWidth = 0; 84 let maxUnscaledHeight = 0; 85 for (const panel of this.panels) { 86 totalUnscaledWidth += panel.width; 87 maxUnscaledHeight = Math.max(maxUnscaledHeight, panel.height); 88 } 89 90 const numPanels = this.panels.length; 91 const scaleForWidth = 92 (availableWidth - (numPanels - 1) * gap) / totalUnscaledWidth; 93 const scaleForHeight = availableHeight / maxUnscaledHeight; 94 return Math.min(scaleForWidth, scaleForHeight); 95 } 96 97 renderThumbnail(thumb, scale) { 98 const style = ` 99 left: ${thumb.x * scale}px; 100 top: ${thumb.y * scale}px; 101 width: ${thumb.width * scale}px; 102 height: ${thumb.height * scale}px; 103 `; 104 105 const bgColor = thumb.themeColor || "var(--bg-header)"; 106 const labelStyle = ` 107 background-color: ${bgColor}; 108 color: contrast-color(${bgColor}); 109 `; 110 111 return html` 112 <div 113 class="thumbnail ${thumb.active ? "active" : ""}" 114 style=${style} 115 @click=${(e) => this.handleThumbnailClick(e, thumb)} 116 > 117 ${thumb.screenshotUrl 118 ? html`<img 119 src=${thumb.screenshotUrl} 120 alt=${thumb.title || "Webview"} 121 />` 122 : ""} 123 <div class="label" style=${labelStyle}> 124 ${thumb.title || "Untitled"} 125 </div> 126 </div> 127 `; 128 } 129 130 renderPanel(panel, scale) { 131 const wrapperStyle = ` 132 width: ${panel.width * scale}px; 133 height: ${panel.height * scale}px; 134 `; 135 const panelStyle = ` 136 width: ${panel.width * scale}px; 137 height: ${panel.height * scale}px; 138 `; 139 140 return html` 141 <div class="panel-wrapper" style=${wrapperStyle}> 142 <div class="panel" style=${panelStyle}> 143 ${panel.thumbnails.map((thumb) => this.renderThumbnail(thumb, scale))} 144 </div> 145 </div> 146 `; 147 } 148 149 render() { 150 const scale = this.calculateScale(); 151 152 return html` 153 <link rel="stylesheet" href="overview.css" /> 154 <div class="container" @click=${this.handleContainerClick}> 155 <div class="panels"> 156 ${this.panels.map((panel) => this.renderPanel(panel, scale))} 157 </div> 158 </div> 159 `; 160 } 161} 162 163customElements.define("panel-overview", PanelOverview);