blob: 461fd40eafd76b04da9eac02a6a33f058b3adbf7 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '/strings.m.js';
import './data_section.js';
import '//resources/cr_elements/cr_button/cr_button.js';
import type {CrButtonElement} from '//resources/cr_elements/cr_button/cr_button.js';
import {assert} from '//resources/js/assert.js';
import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js';
import type {BatchUploadAccountInfo, BatchUploadData, DataContainer} from './batch_upload.js';
import {getCss} from './batch_upload_app.css.js';
import {getHtml} from './batch_upload_app.html.js';
import {BatchUploadBrowserProxyImpl} from './browser_proxy.js';
import type {BatchUploadBrowserProxy} from './browser_proxy.js';
function createEmptyAccountInfo() {
return {
email: '',
dataPictureUrl: '',
};
}
export interface BatchUploadAppElement {
$: {
batchUploadDialog: HTMLElement,
dataContainer: HTMLElement,
dataSections: HTMLElement,
saveButton: CrButtonElement,
cancelButton: CrButtonElement,
};
}
const BatchUploadAppElementBase = I18nMixinLit(CrLitElement);
export class BatchUploadAppElement extends BatchUploadAppElementBase {
static get is() {
return 'batch-upload-app';
}
static override get styles() {
return getCss();
}
override render() {
return getHtml.bind(this)();
}
static override get properties() {
return {
accountInfo_: {type: Object},
dialogSubtitle_: {type: String},
dataSections_: {type: Array},
isSaveEnabled_: {type: Boolean},
};
}
private batchUploadBrowserProxy_: BatchUploadBrowserProxy =
BatchUploadBrowserProxyImpl.getInstance();
// Account information displayed in the view.
protected accessor accountInfo_: BatchUploadAccountInfo =
createEmptyAccountInfo();
protected accessor dialogSubtitle_: string = '';
// Stores the input coming from the browser initialized in
// `initializeInputAndOutputData_()`.
protected accessor dataSections_: DataContainer[] = [];
// State of the section toggles, this is needed to control the save button
// state.
protected dataSectionsToggles_: boolean[] = [];
// Whether save to account button is enabled or not.
protected accessor isSaveEnabled_: boolean = true;
// Observes the size of `dataSections` to know whether to show a border or not
// if scrolling is possible.
private resizeObserver_: ResizeObserver|null = null;
override connectedCallback() {
super.connectedCallback();
this.addResizeObserver_();
this.batchUploadBrowserProxy_.callbackRouter.sendBatchUploadData
.addListener((batchUploadData: BatchUploadData) => {
this.accountInfo_ = batchUploadData.accountInfo;
this.dialogSubtitle_ = batchUploadData.dialogSubtitle;
// Populate data with input from browser.
this.initializeInputAndOutputData_(batchUploadData.dataContainers);
this.requestUpdate();
this.updateViewHeight_();
});
}
override disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver_!.disconnect();
}
private addResizeObserver_() {
const dataContainer = this.$.dataContainer;
this.resizeObserver_ = new ResizeObserver(() => {
const scrollbarVisible =
dataContainer.scrollHeight > dataContainer.clientHeight;
// Defer style changes to after the browser repaints to avoid triggering a
// resize loop. Applying the style changes would not interfere with the
// previously computed value of `scrollbarVisible` on the newer
// notification. Check crbug.com/397366630.
requestAnimationFrame(() => {
// Show the container border line if the scroll bar is visible.
dataContainer.classList.toggle('border-line', scrollbarVisible);
// Adapt the section padding if the scrollbar is visible by overriding
// the value (removing the scrollbar width).
this.$.dataSections.classList.toggle(
'data-sections-with-scrollbar', scrollbarVisible);
});
});
this.resizeObserver_.observe(dataContainer);
}
// Request the browser to update the native view to match the current height
// of the web view.
protected async updateViewHeight_() {
await this.updateComplete;
const height = this.$.batchUploadDialog.clientHeight;
this.batchUploadBrowserProxy_.handler.updateViewHeight(height);
}
protected onSectionToggleChanged_(e: Event) {
const customEvent = e as CustomEvent;
const sectionIndex = Number((e.target as HTMLElement).dataset['index']);
this.dataSectionsToggles_[sectionIndex] = customEvent.detail.toggle;
this.updateSaveEnabled_();
}
// Initializes the input structure that the Ui uses for display.
// Expected to be called once.
private initializeInputAndOutputData_(containerList: DataContainer[]) {
assert(this.dataSections_.length === 0);
// A data container is equivalent to a section in the Ui.
this.dataSections_ = containerList;
// Sections are enabled by default.
this.dataSectionsToggles_ = Array(this.dataSections_.length).fill(true);
// There should be at least one section.
assert(
this.dataSections_ !== undefined && this.dataSections_.length !== 0,
'There should at least be one section to show.');
}
protected close_() {
this.batchUploadBrowserProxy_.handler.close();
}
private updateSaveEnabled_() {
// If at least one section is not disabled, then save is allowed.
this.isSaveEnabled_ =
this.dataSectionsToggles_.some((value: boolean) => value);
}
protected saveToAccount_() {
assert(this.isSaveEnabled_);
const idsToMove: number[][] = [];
// Get the section element list.
const dataSections = this.shadowRoot.querySelectorAll(`data-section`);
// Getting the output from each section.
for (let i = 0; i < dataSections.length; ++i) {
const selectedIds = dataSections[i]!.dataSelected;
idsToMove.push([...selectedIds]);
}
this.batchUploadBrowserProxy_.handler.saveToAccount(idsToMove);
}
}
declare global {
interface HTMLElementTagNameMap {
'batch-upload-app': BatchUploadAppElement;
}
}
customElements.define(BatchUploadAppElement.is, BatchUploadAppElement);