blob: 55f2bc43f74189260c3e122f62cf96ce1d3d37fc [file] [log] [blame]
// Copyright 2021 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.
/**
* @fileoverview UI element of a download item.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import './strings.m.js';
import {assert} from 'chrome://resources/js/assert_ts.js';
import {CustomElement} from 'chrome://resources/js/custom_element.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {DownloadItem, DownloadMode, DownloadState} from './download_shelf.mojom-webui.js';
import {DownloadShelfApiProxy, DownloadShelfApiProxyImpl} from './download_shelf_api_proxy.js';
enum DisplayMode {
// Shows icon + filename + context menu button.
kNormal = 'normal',
// Shows icon + warning text + discard button + context menu button.
kWarn = 'warn',
// Shows icon + warning text + keep button + discard button.
kWarnKeep = 'warn-keep',
}
export class DownloadItemElement extends CustomElement {
static get template() {
return `{__html_template__}`;
}
private item_: DownloadItem;
private downloadUpdated_: boolean = false;
private opening_: boolean = false;
opened: boolean;
private apiProxy_: DownloadShelfApiProxy;
constructor() {
super();
this.opened = false;
this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
this.$('#shadow-mask')!.addEventListener(
'click', () => this.onOpenButtonClick_());
this.$('#dropdown-button')!.addEventListener(
'click', e => this.onDropdownButtonClick_(e));
const discardButton = this.$('#discard-button') as HTMLElement;
discardButton.innerText = loadTimeData.getString('discardButtonText');
discardButton.addEventListener('click', () => this.onDiscardButtonClick_());
this.$('#keep-button')!.addEventListener(
'click', () => this.onKeepButtonClick_());
this.addEventListener('contextmenu', e => this.onContextMenu_(e));
this.$('.progress-indicator')!.addEventListener('animationend', () => {
this.$('.progress-indicator')!.classList.remove(
'download-complete-animation');
});
}
onDownloadUpdated(item: DownloadItem) {
this.downloadUpdated_ = true;
this.item_ = item;
this.update_();
}
set item(value: DownloadItem) {
if (this.item_ === value) {
return;
}
this.item_ = value;
this.update_();
}
get item(): DownloadItem {
return this.item_;
}
private get clampedWarningText_(): string {
// Views uses ui/gfx/text_elider.cc to elide text given a maximum width.
// For simplicity, we instead elide text by restricting text length.
const maxFilenameLength = 19;
const warningText = this.item.warningText;
if (!warningText) {
return '';
}
const filepath = this.item.fileNameDisplayString;
const filename = filepath.substring(filepath.lastIndexOf('/') + 1);
return warningText.replace(
filename, this.elideFilename_(filename, maxFilenameLength));
}
set opening(value: boolean) {
if (this.opening_ !== value) {
this.opening_ = value;
this.update_();
}
}
private update_() {
const item = this.item_;
if (!item) {
return;
}
const downloadElement = this.$('.download-item') as HTMLElement;
const filePath = item.fileNameDisplayString;
let fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
if (this.opening_) {
fileName = loadTimeData.getStringF('downloadStatusOpeningText', fileName);
}
(this.$('#filename') as HTMLElement).innerText = fileName;
const statusTextElement = this.$('#status-text') as HTMLElement;
const statusText = (!item.shouldPromoteOrigin || !item.originalUrl.url) ?
item.statusText :
new URL(item.originalUrl.url).origin;
statusTextElement.innerText = statusText;
downloadElement.dataset['state'] = item.state.toString();
if (item.mode === DownloadMode.kNormal) {
switch (item.state) {
case DownloadState.kInProgress:
this.progress = item.totalBytes > 0 ?
Number(item.receivedBytes) / Number(item.totalBytes) :
0;
break;
case DownloadState.kComplete:
this.progress = 1;
// Only start animation if it's called from OnDownloadUpdated.
if (this.downloadUpdated_) {
this.$('.progress-indicator')!.classList.add(
'download-complete-animation');
}
break;
case DownloadState.kInterrupted:
this.progress = 0;
break;
}
}
if (item.isPaused) {
downloadElement.dataset['paused'] = 'true';
} else {
delete downloadElement.dataset['paused'];
}
this.apiProxy_.getFileIcon(item.id).then(icon => {
(this.$('#file-icon') as HTMLImageElement).src = icon;
});
if (item.mode === DownloadMode.kNormal) {
downloadElement.dataset['displayMode'] = DisplayMode.kNormal;
} else if (
item.mode === DownloadMode.kDangerous ||
item.mode === DownloadMode.kMixedContentWarn) {
downloadElement.dataset['displayMode'] = DisplayMode.kWarnKeep;
} else {
downloadElement.dataset['displayMode'] = DisplayMode.kWarn;
}
(this.$('#keep-button') as HTMLElement).innerText =
item.warningConfirmButtonText;
(this.$('#warning-text') as HTMLElement).innerText =
this.clampedWarningText_;
this.downloadUpdated_ = false;
}
set progress(value: number) {
(this.$('.progress') as HTMLElement)
.style.setProperty('--download-progress', value.toString());
}
private onContextMenu_(e: MouseEvent) {
this.apiProxy_.showContextMenu(
this.item.id, e.clientX, e.clientY, Date.now());
}
private onDropdownButtonClick_(e: Event) {
// TODO(crbug.com/1182529): Switch to down caret icon when context menu is
// open.
const rect = (e.target as Element).getBoundingClientRect();
this.apiProxy_.showContextMenu(
this.item.id, rect.left, rect.top, Date.now());
}
private onDiscardButtonClick_() {
this.apiProxy_.discardDownload(this.item.id);
}
private onKeepButtonClick_() {
this.apiProxy_.keepDownload(this.item.id);
}
/**
* Elide a filename to a maximum length.
* The extension of the filename will be kept if it has one.
* @param s A filename.
* @param maxlen The maximum length after elided.
*/
private elideFilename_(s: string, maxlen: number): string {
assert(maxlen > 6);
if (s.length <= maxlen) {
return s;
}
const extIndex = s.lastIndexOf('.');
if (extIndex === -1) {
// |s| does not have an extension.
return s.substr(0, maxlen - 3) + '...';
} else {
const subfix = '...' + s.substr(extIndex);
return s.substr(0, maxlen - subfix.length) + subfix;
}
}
private onOpenButtonClick_() {
if (this.opening_) {
return;
}
if (this.item_.mode === DownloadMode.kNormal) {
this.apiProxy_.openDownload(this.item.id);
} else {
// TODO(crbug.com/1182529): Handle the scanning case.
}
}
}
customElements.define('download-item', DownloadItemElement);
declare global {
interface HTMLElementTagNameMap {
'download-item': DownloadItemElement;
}
}