UNPKG

chrome-devtools-frontend

Version:
282 lines (259 loc) 9.96 kB
// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable rulesdir/no-lit-render-outside-of-view */ import '../../../ui/legacy/legacy.js'; import * as Common from '../../../core/common/common.js'; import * as i18n from '../../../core/i18n/i18n.js'; import type * as Platform from '../../../core/platform/platform.js'; import {html, nothing, render} from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import CSSPropertyDocsViewStyles from './cssPropertyDocsView.css.js'; const UIStrings = { /** * @description Text for button that redirects to CSS property documentation. */ learnMore: 'Learn more', /** * @description Text for a checkbox to turn off the CSS property documentation. */ dontShow: 'Don\'t show', /** * @description Text indicating that the CSS property has limited availability across major browsers. */ limitedAvailability: 'Limited availability across major browsers', /** * @description Text indicating that the CSS property has limited availability across major browsers, with a list of unsupported browsers. * @example {Firefox} PH1 * @example {Safari on iOS} PH1 * @example {Chrome, Firefox on Android, or Safari} PH1 */ limitedAvailabilityInBrowsers: 'Limited availability across major browsers (not fully implemented in {PH1})', /** * @description Text to display a combination of browser name and platform name. * @example {Safari} PH1 * @example {iOS} PH2 */ browserOnPlatform: '{PH1} on {PH2}', /** * @description Text indicating that the CSS property is newly available across major browsers since a certain time. * @example {September 2015} PH1 */ newlyAvailableSince: 'Newly available across major browsers (`Baseline` since {PH1})', /** * @description Text indicating that the CSS property is widely available across major browsers since a certain time. * @example {September 2015} PH1 * @example {an unknown date} PH1 */ widelyAvailableSince: 'Widely available across major browsers (`Baseline` since {PH1})', /** * @description Text indicating that a specific date is not known. */ unknownDate: 'an unknown date', } as const; const str_ = i18n.i18n.registerUIStrings('panels/elements/components/CSSPropertyDocsView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const BASELINE_HIGH_AVAILABILITY_ICON = '../../../Images/baseline-high-availability.svg' as Platform.DevToolsPath.RawPathString; const BASELINE_LOW_AVAILABILITY_ICON = '../../../Images/baseline-low-availability.svg' as Platform.DevToolsPath.RawPathString; const BASELINE_LIMITED_AVAILABILITY_ICON = '../../../Images/baseline-limited-availability.svg' as Platform.DevToolsPath.RawPathString; const getBaselineIconPath = (baseline: Baseline): Platform.DevToolsPath.UrlString => { let relativePath: string; switch (baseline.status) { case BaselineStatus.HIGH: relativePath = BASELINE_HIGH_AVAILABILITY_ICON; break; case BaselineStatus.LOW: relativePath = BASELINE_LOW_AVAILABILITY_ICON; break; default: relativePath = BASELINE_LIMITED_AVAILABILITY_ICON; } return new URL(relativePath, import.meta.url).toString() as Platform.DevToolsPath.UrlString; }; const enum BrowserId { C = 'C', CA = 'CA', E = 'E', FF = 'FF', FFA = 'FFA', S = 'S', SM = 'SM', } const allBrowserIds = new Set<BrowserId>( [BrowserId.C, BrowserId.CA, BrowserId.E, BrowserId.FF, BrowserId.FFA, BrowserId.S, BrowserId.SM]); const enum BrowserPlatform { DESKTOP = 'desktop', ANDROID = 'Android', MACOS = 'macOS', IOS = 'iOS', } const browserIdToNameAndPlatform = new Map([ [BrowserId.C, {name: 'Chrome', platform: BrowserPlatform.DESKTOP}], [BrowserId.CA, {name: 'Chrome', platform: BrowserPlatform.ANDROID}], [BrowserId.E, {name: 'Edge', platform: BrowserPlatform.DESKTOP}], [BrowserId.FF, {name: 'Firefox', platform: BrowserPlatform.DESKTOP}], [BrowserId.FFA, {name: 'Firefox', platform: BrowserPlatform.ANDROID}], [BrowserId.S, {name: 'Safari', platform: BrowserPlatform.MACOS}], [BrowserId.SM, {name: 'Safari', platform: BrowserPlatform.IOS}], ]); function formatBrowserList(browserNames: Map<string, BrowserPlatform[]>): string { const formatter = new Intl.ListFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, { style: 'long', type: 'disjunction', }); return formatter.format(browserNames.entries().map( ([name, platforms]) => platforms.length !== 1 || platforms[0] === BrowserPlatform.DESKTOP ? name : i18nString(UIStrings.browserOnPlatform, {PH1: name, PH2: platforms[0]}))); } const formatBaselineDate = (date?: string): string => { if (!date) { return i18nString(UIStrings.unknownDate); } const parsedDate = new Date(date); const formatter = new Intl.DateTimeFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, { month: 'long', year: 'numeric', }); return formatter.format(parsedDate); }; const getBaselineMissingBrowsers = (browsers: string[]): Map<string, BrowserPlatform[]> => { const browserIds = browsers.map(v => v.replace(/\d*$/, '')); const missingBrowserIds = allBrowserIds.difference(new Set(browserIds)); const missingBrowsers = new Map(); for (const id of missingBrowserIds) { const browserInfo = browserIdToNameAndPlatform.get(id); if (browserInfo) { const {name, platform} = browserInfo; missingBrowsers.set(name, [...(missingBrowsers.get(name) ?? []), platform]); } } return missingBrowsers; }; const getBaselineText = (baseline: Baseline, browsers?: string[]): string => { if (baseline.status === BaselineStatus.LIMITED) { const missingBrowsers = browsers && getBaselineMissingBrowsers(browsers); if (missingBrowsers) { return i18nString(UIStrings.limitedAvailabilityInBrowsers, {PH1: formatBrowserList(missingBrowsers)}); } return i18nString(UIStrings.limitedAvailability); } if (baseline.status === BaselineStatus.LOW) { return i18nString(UIStrings.newlyAvailableSince, {PH1: formatBaselineDate(baseline.baseline_low_date)}); } return i18nString(UIStrings.widelyAvailableSince, {PH1: formatBaselineDate(baseline.baseline_high_date)}); }; export const enum BaselineStatus { LIMITED = 'false', LOW = 'low', HIGH = 'high', } /** * An object containing Baseline (https://web.dev/baseline, * https://web-platform-dx.github.io/web-features/) information about the feature's browser * compatibility. */ interface Baseline { /** * The Baseline status of the feature: * - `"false"` — limited availability across major browsers * - `"low"` — newly available across major browsers * - `"high"` — widely available across major browsers */ status: BaselineStatus; /** * A date in the format `YYYY-MM-DD` representing when the feature became newly available, * or `undefined` if it hasn't yet reached that status. */ // eslint-disable-next-line @typescript-eslint/naming-convention baseline_low_date?: string; /** * A date in the format `YYYY-MM-DD` representing when the feature became widely available, * or `undefined` if it hasn't yet reached that status. * * The widely available date is always 30 months after the newly available date. */ // eslint-disable-next-line @typescript-eslint/naming-convention baseline_high_date?: string; } export interface CSSProperty { name: string; description?: string; baseline?: Baseline; browsers?: string[]; references?: Array<{ name: string, url: string, }>; } export class CSSPropertyDocsView extends HTMLElement { readonly #shadow = this.attachShadow({mode: 'open'}); readonly #cssProperty: CSSProperty; constructor(cssProperty: CSSProperty) { super(); this.#cssProperty = cssProperty; this.#render(); } #dontShowChanged(e: Event): void { const showDocumentation = !(e.target as HTMLInputElement).checked; Common.Settings.Settings.instance() .moduleSetting('show-css-property-documentation-on-hover') .set(showDocumentation); } #render(): void { const {description, references, baseline, browsers} = this.#cssProperty; const link = references?.[0].url; // Disabled until https://crbug.com/1079231 is fixed. // clang-format off render(html` <style>${CSSPropertyDocsViewStyles}</style> <div class="docs-popup-wrapper"> ${description ? html` <div id="description"> ${description} </div> ` : nothing} ${baseline ? html` <div id="baseline" class="docs-popup-section"> <img id="baseline-icon" src=${getBaselineIconPath(baseline)} role="presentation" > <span> ${getBaselineText(baseline, browsers)} </span> </div> ` : nothing} ${link ? html` <div class="docs-popup-section footer"> <x-link id="learn-more" href=${link} class="clickable underlined unbreakable-text" > ${i18nString(UIStrings.learnMore)} </x-link> <devtools-checkbox @change=${this.#dontShowChanged} jslog=${VisualLogging.toggle('css-property-doc').track({ change: true })}> ${i18nString(UIStrings.dontShow)} </devtools-checkbox> </div> ` : nothing} </div> `, this.#shadow, { host: this, }); // clang-format on } } customElements.define('devtools-css-property-docs-view', CSSPropertyDocsView); declare global { interface HTMLElementTagNameMap { 'devtools-css-property-docs-view': CSSPropertyDocsView; } }