chrome-devtools-frontend
Version:
Chrome DevTools UI
199 lines (179 loc) • 6.86 kB
text/typescript
// Copyright 2025 The Chromium Authors. All rights reserved.
// 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 */
/* eslint-disable rulesdir/no-imperative-dom-api */
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Lit from '../../ui/lit/lit.js';
import commonStyles from './common.css.js';
const {html} = Lit;
const UIStrings = {
/**
*@description Header text for the feature reminder dialog.
*/
thingsToConsider: 'Things to consider',
/**
*@description Text for the learn more button in the feature reminder dialog.
*/
learnMore: 'Learn more',
/**
*@description Text for the cancel button in the feature reminder dialog.
*/
cancel: 'Cancel',
/**
*@description Text for the cancel button in the "type to allow" dialog.
*/
allow: 'Allow',
/**
*@description Text for the got it button in the feature reminder dialog.
*/
gotIt: 'Got it',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/common/common.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class FreDialog {
static show({header, reminderItems, onLearnMoreClick, ariaLabel, learnMoreButtonTitle}: {
header: {
iconName: string,
text: Platform.UIString.LocalizedString,
},
reminderItems: Array<{
iconName: string,
content: Platform.UIString.LocalizedString|Lit.LitTemplate,
}>,
onLearnMoreClick: () => void,
ariaLabel?: string,
learnMoreButtonTitle?: string,
}): Promise<boolean> {
const dialog = new UI.Dialog.Dialog();
if (ariaLabel) {
dialog.setAriaLabel(ariaLabel);
}
const result = Promise.withResolvers<boolean>();
// clang-format off
Lit.render(html`
<div class="fre-disclaimer">
<style>
${commonStyles}
</style>
<header>
<div class="header-icon-container">
<devtools-icon name=${header.iconName}></devtools-icon>
</div>
<h2 tabindex="-1">
${header.text}
</h2>
</header>
<main class="reminder-container">
<h3>${i18nString(UIStrings.thingsToConsider)}</h3>
${reminderItems.map(reminderItem => html`
<div class="reminder-item">
<devtools-icon class="reminder-icon" name=${reminderItem.iconName}></devtools-icon>
<span>${reminderItem.content}</span>
</div>
`)}
</main>
<footer>
<devtools-button
@click=${onLearnMoreClick}
.jslogContext=${'fre-disclaimer.learn-more'}
.variant=${Buttons.Button.Variant.OUTLINED}>
${learnMoreButtonTitle ?? i18nString(UIStrings.learnMore)}
</devtools-button>
<div class="right-buttons">
<devtools-button
@click=${() => {
dialog.hide();
result.resolve(false);
}}
.jslogContext=${'fre-disclaimer.cancel'}
.variant=${Buttons.Button.Variant.TONAL}>
${i18nString(UIStrings.cancel)}
</devtools-button>
<devtools-button
@click=${() => {
dialog.hide();
result.resolve(true);
}}
.jslogContext=${'fre-disclaimer.continue'}
.variant=${Buttons.Button.Variant.PRIMARY}>
${i18nString(UIStrings.gotIt)}
</devtools-button>
</div>
</footer>
</div>`, dialog.contentElement);
// clang-format on
dialog.setOutsideClickCallback(ev => {
ev.consume(true); // true = preventDefault()
dialog.hide();
result.resolve(false);
});
dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MEASURE_CONTENT);
dialog.setDimmed(true);
dialog.show();
return result.promise;
}
private constructor() {
}
}
export class TypeToAllowDialog {
static async show(options: {
jslogContext: {
dialog: string,
input: string,
},
header: Platform.UIString.LocalizedString,
message: Platform.UIString.LocalizedString,
typePhrase: Platform.UIString.LocalizedString,
inputPlaceholder: Platform.UIString.LocalizedString,
}): Promise<boolean> {
const dialog = new UI.Dialog.Dialog(options.jslogContext.dialog);
dialog.setMaxContentSize(new UI.Geometry.Size(504, 340));
dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SET_EXACT_WIDTH_MAX_HEIGHT);
dialog.setDimmed(true);
const shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(dialog.contentElement, {cssFile: commonStyles});
const content = shadowRoot.createChild('div', 'type-to-allow-dialog');
const result = await new Promise<boolean>(resolve => {
const header = content.createChild('div', 'header');
header.createChild('div', 'title').textContent = options.header;
const closeButton = header.createChild('dt-close-button', 'dialog-close-button');
closeButton.setTabbable(true);
self.onInvokeElement(closeButton, event => {
dialog.hide();
event.consume(true);
resolve(false);
});
closeButton.setSize(Buttons.Button.Size.SMALL);
content.createChild('div', 'message').textContent = options.message;
const input = UI.UIUtils.createInput('text-input', 'text', options.jslogContext.input);
input.placeholder = options.inputPlaceholder;
content.appendChild(input);
const buttonsBar = content.createChild('div', 'button');
const cancelButton =
UI.UIUtils.createTextButton(i18nString(UIStrings.cancel), () => resolve(false), {jslogContext: 'cancel'});
const allowButton = UI.UIUtils.createTextButton(i18nString(UIStrings.allow), () => {
resolve(input.value === options.typePhrase);
}, {jslogContext: 'confirm', variant: Buttons.Button.Variant.PRIMARY});
allowButton.disabled = true;
buttonsBar.appendChild(allowButton);
buttonsBar.appendChild(cancelButton);
input.addEventListener('input', () => {
allowButton.disabled = !Boolean(input.value);
}, false);
input.addEventListener('paste', e => e.preventDefault());
input.addEventListener('drop', e => e.preventDefault());
dialog.setOutsideClickCallback(event => {
event.consume();
resolve(false);
});
dialog.show();
Host.userMetrics.actionTaken(Host.UserMetrics.Action.SelfXssWarningDialogShown);
});
dialog.hide();
return result;
}
}