1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5export class CSSColor { 6 static _cache = new Map(); 7 8 static get(name) { 9 let color = this._cache.get(name); 10 if (color !== undefined) return color; 11 const style = getComputedStyle(document.body); 12 color = style.getPropertyValue(`--${name}`); 13 if (color === undefined) { 14 throw new Error(`CSS color does not exist: ${name}`); 15 } 16 color = color.trim(); 17 this._cache.set(name, color); 18 return color; 19 } 20 21 static reset() { 22 this._cache.clear(); 23 } 24 25 static get backgroundColor() { 26 return this.get('background-color'); 27 } 28 static get surfaceColor() { 29 return this.get('surface-color'); 30 } 31 static get primaryColor() { 32 return this.get('primary-color'); 33 } 34 static get secondaryColor() { 35 return this.get('secondary-color'); 36 } 37 static get onSurfaceColor() { 38 return this.get('on-surface-color'); 39 } 40 static get onBackgroundColor() { 41 return this.get('on-background-color'); 42 } 43 static get onPrimaryColor() { 44 return this.get('on-primary-color'); 45 } 46 static get onSecondaryColor() { 47 return this.get('on-secondary-color'); 48 } 49 static get defaultColor() { 50 return this.get('default-color'); 51 } 52 static get errorColor() { 53 return this.get('error-color'); 54 } 55 static get mapBackgroundColor() { 56 return this.get('map-background-color'); 57 } 58 static get timelineBackgroundColor() { 59 return this.get('timeline-background-color'); 60 } 61 static get red() { 62 return this.get('red'); 63 } 64 static get green() { 65 return this.get('green'); 66 } 67 static get yellow() { 68 return this.get('yellow'); 69 } 70 static get blue() { 71 return this.get('blue'); 72 } 73 74 static get orange() { 75 return this.get('orange'); 76 } 77 78 static get violet() { 79 return this.get('violet'); 80 } 81 82 static at(index) { 83 return this.list[index % this.list.length]; 84 } 85 86 static darken(hexColorString, amount = -50) { 87 if (hexColorString[0] !== '#') { 88 throw new Error(`Unsupported color: ${hexColorString}`); 89 } 90 let color = parseInt(hexColorString.substring(1), 16); 91 let b = Math.min(Math.max((color & 0xFF) + amount, 0), 0xFF); 92 let g = Math.min(Math.max(((color >> 8) & 0xFF) + amount, 0), 0xFF); 93 let r = Math.min(Math.max(((color >> 16) & 0xFF) + amount, 0), 0xFF); 94 color = (r << 16) + (g << 8) + b; 95 return `#${color.toString(16).padStart(6, '0')}`; 96 } 97 98 static get list() { 99 if (!this._colors) { 100 this._colors = [ 101 this.green, 102 this.violet, 103 this.orange, 104 this.yellow, 105 this.primaryColor, 106 this.red, 107 this.blue, 108 this.yellow, 109 this.secondaryColor, 110 this.darken(this.green), 111 this.darken(this.violet), 112 this.darken(this.orange), 113 this.darken(this.yellow), 114 this.darken(this.primaryColor), 115 this.darken(this.red), 116 this.darken(this.blue), 117 this.darken(this.yellow), 118 this.darken(this.secondaryColor), 119 ]; 120 } 121 return this._colors; 122 } 123} 124 125import {DOM} from '../../js/web-api-helper.mjs'; 126 127const SVGNamespace = 'http://www.w3.org/2000/svg'; 128export class SVG { 129 static element(type, classes) { 130 const node = document.createElementNS(SVGNamespace, type); 131 if (classes !== undefined) DOM.addClasses(node, classes); 132 return node; 133 } 134 135 static svg(classes) { 136 return this.element('svg', classes); 137 } 138 139 static rect(classes) { 140 return this.element('rect', classes); 141 } 142 143 static g(classes) { 144 return this.element('g', classes); 145 } 146} 147 148export function $(id) { 149 return document.querySelector(id) 150} 151 152import {V8CustomElement} from '../../js/web-api-helper.mjs' 153 154export class CollapsableElement extends V8CustomElement { 155 constructor(templateText) { 156 super(templateText); 157 this._hasPendingUpdate = false; 158 this._closer.onclick = _ => this._requestUpdateIfVisible(); 159 } 160 161 get _closer() { 162 return this.$('#closer'); 163 } 164 165 get _contentIsVisible() { 166 return !this._closer.checked; 167 } 168 169 hide() { 170 if (this._contentIsVisible) { 171 this._closer.checked = true; 172 this._requestUpdateIfVisible(); 173 } 174 } 175 176 show() { 177 if (!this._contentIsVisible) { 178 this._closer.checked = false; 179 this._requestUpdateIfVisible(); 180 } 181 this.scrollIntoView({behavior: 'smooth', block: 'center'}); 182 } 183 184 requestUpdate(useAnimation = false) { 185 // A pending update will be resolved later, no need to try again. 186 if (this._hasPendingUpdate) return; 187 this._hasPendingUpdate = true; 188 this._requestUpdateIfVisible(useAnimation); 189 } 190 191 _requestUpdateIfVisible(useAnimation = true) { 192 if (!this._contentIsVisible) return; 193 return super.requestUpdate(useAnimation); 194 } 195 196 forceUpdate() { 197 this._hasPendingUpdate = false; 198 super.forceUpdate(); 199 } 200} 201 202export class ExpandableText { 203 constructor(node, string, limit = 200) { 204 this._node = node; 205 this._string = string; 206 this._delta = limit / 2; 207 this._start = 0; 208 this._end = string.length; 209 this._button = this._createExpandButton(); 210 this.expand(); 211 } 212 213 _createExpandButton() { 214 const button = DOM.element('button'); 215 button.innerText = '...'; 216 button.onclick = (e) => { 217 e.stopImmediatePropagation(); 218 this.expand(e.shiftKey); 219 }; 220 button.title = 'Expand text. Use SHIFT-click to show all.' 221 return button; 222 } 223 224 expand(showAll = false) { 225 DOM.removeAllChildren(this._node); 226 this._start = this._start + this._delta; 227 this._end = this._end - this._delta; 228 if (this._start >= this._end || showAll) { 229 this._node.innerText = this._string; 230 this._button.onclick = undefined; 231 return; 232 } 233 this._node.appendChild(DOM.text(this._string.substring(0, this._start))); 234 this._node.appendChild(this._button); 235 this._node.appendChild( 236 DOM.text(this._string.substring(this._end, this._string.length))); 237 } 238} 239 240export class Chunked { 241 constructor(iterable, limit) { 242 this._iterator = iterable[Symbol.iterator](); 243 this._limit = limit; 244 } 245 246 * next(limit = undefined) { 247 for (let i = 0; i < (limit ?? this._limit); i++) { 248 const {value, done} = this._iterator.next(); 249 if (done) { 250 this._iterator = undefined; 251 return; 252 }; 253 yield value; 254 } 255 } 256 257 get hasMore() { 258 return this._iterator !== undefined; 259 } 260} 261 262export class LazyTable { 263 constructor(table, rowData, rowElementCreator, limit = 100) { 264 this._table = table; 265 this._chunkedRowData = new Chunked(rowData, limit); 266 this._rowElementCreator = rowElementCreator; 267 if (table.tBodies.length == 0) { 268 table.appendChild(DOM.tbody()); 269 } else { 270 table.replaceChild(DOM.tbody(), table.tBodies[0]); 271 } 272 if (!table.tFoot) this._addFooter(); 273 table.tFoot.addEventListener('click', this._clickHandler); 274 this._addMoreRows(); 275 } 276 277 _addFooter() { 278 const td = DOM.td(); 279 td.setAttribute('colspan', 100); 280 for (let addCount of [10, 100, 250, 500]) { 281 const button = DOM.element('button'); 282 button.innerText = `+${addCount}`; 283 button.onclick = (e) => this._addMoreRows(addCount); 284 td.appendChild(button); 285 } 286 this._table.appendChild(DOM.element('tfoot')) 287 .appendChild(DOM.tr()) 288 .appendChild(td); 289 } 290 291 _addMoreRows(count = undefined) { 292 const fragment = new DocumentFragment(); 293 for (let row of this._chunkedRowData.next(count)) { 294 const tr = this._rowElementCreator(row); 295 fragment.appendChild(tr); 296 } 297 this._table.tBodies[0].appendChild(fragment); 298 if (!this._chunkedRowData.hasMore) { 299 DOM.removeAllChildren(this._table.tFoot); 300 } 301 } 302} 303 304export function gradientStopsFromGroups( 305 totalLength, maxHeight, groups, colorFn) { 306 const kMaxHeight = maxHeight === '%' ? 100 : maxHeight; 307 const kUnit = maxHeight === '%' ? '%' : 'px'; 308 let increment = 0; 309 let lastHeight = 0.0; 310 const stops = []; 311 for (let group of groups) { 312 const color = colorFn(group.key); 313 increment += group.length; 314 const height = (increment / totalLength * kMaxHeight) | 0; 315 stops.push(`${color} ${lastHeight}${kUnit} ${height}${kUnit}`) 316 lastHeight = height; 317 } 318 return stops; 319} 320 321export * from '../helper.mjs'; 322export * from '../../js/web-api-helper.mjs' 323