// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export class CSSColor { static _cache = new Map(); static get(name) { let color = this._cache.get(name); if (color !== undefined) return color; const style = getComputedStyle(document.body); color = style.getPropertyValue(`--${name}`); if (color === undefined) { throw new Error(`CSS color does not exist: ${name}`); } color = color.trim(); this._cache.set(name, color); return color; } static reset() { this._cache.clear(); } static get backgroundColor() { return this.get('background-color'); } static get surfaceColor() { return this.get('surface-color'); } static get primaryColor() { return this.get('primary-color'); } static get secondaryColor() { return this.get('secondary-color'); } static get onSurfaceColor() { return this.get('on-surface-color'); } static get onBackgroundColor() { return this.get('on-background-color'); } static get onPrimaryColor() { return this.get('on-primary-color'); } static get onSecondaryColor() { return this.get('on-secondary-color'); } static get defaultColor() { return this.get('default-color'); } static get errorColor() { return this.get('error-color'); } static get mapBackgroundColor() { return this.get('map-background-color'); } static get timelineBackgroundColor() { return this.get('timeline-background-color'); } static get red() { return this.get('red'); } static get green() { return this.get('green'); } static get yellow() { return this.get('yellow'); } static get blue() { return this.get('blue'); } static get orange() { return this.get('orange'); } static get violet() { return this.get('violet'); } static at(index) { return this.list[index % this.list.length]; } static darken(hexColorString, amount = -50) { if (hexColorString[0] !== '#') { throw new Error(`Unsupported color: ${hexColorString}`); } let color = parseInt(hexColorString.substring(1), 16); let b = Math.min(Math.max((color & 0xFF) + amount, 0), 0xFF); let g = Math.min(Math.max(((color >> 8) & 0xFF) + amount, 0), 0xFF); let r = Math.min(Math.max(((color >> 16) & 0xFF) + amount, 0), 0xFF); color = (r << 16) + (g << 8) + b; return `#${color.toString(16).padStart(6, '0')}`; } static get list() { if (!this._colors) { this._colors = [ this.green, this.violet, this.orange, this.yellow, this.primaryColor, this.red, this.blue, this.yellow, this.secondaryColor, this.darken(this.green), this.darken(this.violet), this.darken(this.orange), this.darken(this.yellow), this.darken(this.primaryColor), this.darken(this.red), this.darken(this.blue), this.darken(this.yellow), this.darken(this.secondaryColor), ]; } return this._colors; } } import {DOM} from '../../js/web-api-helper.mjs'; const SVGNamespace = 'http://www.w3.org/2000/svg'; export class SVG { static element(type, classes) { const node = document.createElementNS(SVGNamespace, type); if (classes !== undefined) DOM.addClasses(node, classes); return node; } static svg(classes) { return this.element('svg', classes); } static rect(classes) { return this.element('rect', classes); } static path(classes) { return this.element('path', classes); } static g(classes) { return this.element('g', classes); } } export function $(id) { return document.querySelector(id) } import {V8CustomElement} from '../../js/web-api-helper.mjs' export class CollapsableElement extends V8CustomElement { constructor(templateText) { super(templateText); this._hasPendingUpdate = false; this._closer.onclick = _ => this._requestUpdateIfVisible(); } get _closer() { return this.$('#closer'); } get _contentIsVisible() { return !this._closer.checked; } hide() { if (this._contentIsVisible) { this._closer.checked = true; this._requestUpdateIfVisible(); } } show() { if (!this._contentIsVisible) { this._closer.checked = false; this._requestUpdateIfVisible(); } this.scrollIntoView({behavior: 'smooth', block: 'center'}); } requestUpdate(useAnimation = false) { // A pending update will be resolved later, no need to try again. if (this._hasPendingUpdate) return; this._hasPendingUpdate = true; this._requestUpdateIfVisible(useAnimation); } _requestUpdateIfVisible(useAnimation = true) { if (!this._contentIsVisible) return; return super.requestUpdate(useAnimation); } forceUpdate() { this._hasPendingUpdate = false; super.forceUpdate(); } } export class ExpandableText { constructor(node, string, limit = 200) { this._node = node; this._string = string; this._delta = limit / 2; this._start = 0; this._end = string.length; this._button = this._createExpandButton(); this.expand(); } _createExpandButton() { const button = DOM.element('button'); button.innerText = '...'; button.onclick = (e) => { e.stopImmediatePropagation(); this.expand(e.shiftKey); }; button.title = 'Expand text. Use SHIFT-click to show all.' return button; } expand(showAll = false) { DOM.removeAllChildren(this._node); this._start = this._start + this._delta; this._end = this._end - this._delta; if (this._start >= this._end || showAll) { this._node.innerText = this._string; this._button.onclick = undefined; return; } this._node.appendChild(DOM.text(this._string.substring(0, this._start))); this._node.appendChild(this._button); this._node.appendChild( DOM.text(this._string.substring(this._end, this._string.length))); } } export class Chunked { constructor(iterable, limit) { this._iterator = iterable[Symbol.iterator](); this._limit = limit; } * next(limit = undefined) { for (let i = 0; i < (limit ?? this._limit); i++) { const {value, done} = this._iterator.next(); if (done) { this._iterator = undefined; return; }; yield value; } } get hasMore() { return this._iterator !== undefined; } } export class LazyTable { constructor(table, rowData, rowElementCreator, limit = 100) { this._table = table; this._chunkedRowData = new Chunked(rowData, limit); this._rowElementCreator = rowElementCreator; if (table.tBodies.length == 0) { table.appendChild(DOM.tbody()); } else { table.replaceChild(DOM.tbody(), table.tBodies[0]); } if (!table.tFoot) this._addFooter(); table.tFoot.addEventListener('click', this._clickHandler); this._addMoreRows(); } _addFooter() { const td = DOM.td(); td.setAttribute('colspan', 100); for (let addCount of [10, 100, 250, 500]) { const button = DOM.element('button'); button.innerText = `+${addCount}`; button.onclick = (e) => this._addMoreRows(addCount); td.appendChild(button); } this._table.appendChild(DOM.element('tfoot')) .appendChild(DOM.tr()) .appendChild(td); } _addMoreRows(count = undefined) { const fragment = new DocumentFragment(); for (let row of this._chunkedRowData.next(count)) { const tr = this._rowElementCreator(row); fragment.appendChild(tr); } this._table.tBodies[0].appendChild(fragment); if (!this._chunkedRowData.hasMore) { DOM.removeAllChildren(this._table.tFoot); } } } export function gradientStopsFromGroups( totalLength, maxHeight, groups, colorFn) { const kMaxHeight = maxHeight === '%' ? 100 : maxHeight; const kUnit = maxHeight === '%' ? '%' : 'px'; let increment = 0; let lastHeight = 0.0; const stops = []; for (let group of groups) { const color = colorFn(group.key); increment += group.length; const height = (increment / totalLength * kMaxHeight) | 0; stops.push(`${color} ${lastHeight}${kUnit} ${height}${kUnit}`) lastHeight = height; } return stops; } export class Debouncer { constructor(callback, timeout = 250) { this._callback = callback; this._timeout = timeout; this._timeoutId = 0; } callNow(...args) { this.clear(); return this._callback(...args); } call(...args) { this.clear(); this._timeoutId = window.setTimeout(this._callback, this._timeout, ...args); } clear() { clearTimeout(this._timeoutId); } } export * from '../helper.mjs'; export * from '../../js/web-api-helper.mjs'