blob: 1aca986754e5923a154c600b32a4dcbe3914725c [file] [log] [blame]
// Copyright 2019 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.
import './xf_button.js';
import './xf_circular_progress.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {str, util} from '../../common/js/util.js';
import {DisplayPanel} from './xf_display_panel.js';
/** @type {!HTMLTemplateElement} */
const htmlTemplate = html`{__html_template__}`;
/**
* A panel to display the status or progress of a file operation.
* @extends HTMLElement
*/
export class PanelItem extends HTMLElement {
constructor() {
super();
const fragment = htmlTemplate.content.cloneNode(true);
this.attachShadow({mode: 'open'}).appendChild(fragment);
/** @private {Element} */
this.indicator_ = this.shadowRoot.querySelector('#indicator');
/**
* TODO(crbug.com/947388) make this a closure enum.
* @const
*/
this.panelTypeDefault = -1;
this.panelTypeProgress = 0;
this.panelTypeSummary = 1;
this.panelTypeDone = 2;
this.panelTypeError = 3;
this.panelTypeInfo = 4;
this.panelTypeFormatProgress = 5;
this.panelTypeSyncProgress = 6;
/** @private {number} */
this.panelType_ = this.panelTypeDefault;
/** @private @type {?function(Event)} */
this.onclick = this.onClicked_.bind(this);
/** @public {?DisplayPanel} */
this.parent = null;
/**
* Callback that signals events happening in the panel (e.g. click).
* @private @type {!function(*)}
*/
this.signal_ = console.log;
/**
* User specific data, used as a reference to persist any custom
* data that the panel user may want to use in the signal callback.
* e.g. holding the file name(s) used in a copy operation.
* @type {string|Object}
*/
this.userData = null;
}
/**
* Remove an element from the panel using it's id.
* @return {?Element}
* @private
*/
removePanelElementById_(id) {
const element = this.shadowRoot.querySelector(id);
if (element) {
element.remove();
}
return element;
}
/**
* Sets up the different panel types. Panels have per-type configuration
* templates, but can be further customized using individual attributes.
* @param {number} type The enumerated panel type to set up.
* @private
*/
setPanelType(type) {
this.setAttribute('detailed-panel', 'detailed-panel');
if (this.panelType_ === type) {
return;
}
// Remove the indicators/buttons that can change.
this.removePanelElementById_('#indicator');
let element = this.removePanelElementById_('#primary-action');
if (element) {
element.onclick = null;
}
element = this.removePanelElementById_('#secondary-action');
if (element) {
element.onclick = null;
}
// Mark the indicator as empty so it recreates on setAttribute.
this.setAttribute('indicator', 'empty');
const buttonSpacer = this.shadowRoot.querySelector('#button-gap');
// Default the text host to use an alert role.
const textHost = assert(this.shadowRoot.querySelector('.xf-panel-text'));
textHost.setAttribute('role', 'alert');
// Setup the panel configuration for the panel type.
// TOOD(crbug.com/947388) Simplify this switch breaking out common cases.
/** @type {?Element} */
let primaryButton = null;
/** @type {?Element} */
let secondaryButton = null;
switch (type) {
case this.panelTypeProgress:
this.setAttribute('indicator', 'progress');
secondaryButton = document.createElement('xf-button');
secondaryButton.id = 'secondary-action';
secondaryButton.onclick = assert(this.onclick);
secondaryButton.dataset.category = 'cancel';
secondaryButton.setAttribute('aria-label', str('CANCEL_LABEL'));
buttonSpacer.insertAdjacentElement('afterend', secondaryButton);
break;
case this.panelTypeSummary:
this.setAttribute('indicator', 'largeprogress');
primaryButton = document.createElement('xf-button');
primaryButton.id = 'primary-action';
primaryButton.dataset.category = 'expand';
primaryButton.setAttribute('aria-label', str('FEEDBACK_EXPAND_LABEL'));
// Remove the 'alert' role to stop screen readers repeatedly
// reading each progress update.
textHost.setAttribute('role', '');
buttonSpacer.insertAdjacentElement('afterend', primaryButton);
break;
case this.panelTypeDone:
this.setAttribute('indicator', 'status');
this.setAttribute('status', 'success');
primaryButton = document.createElement('xf-button');
primaryButton.id = 'primary-action';
primaryButton.onclick = assert(this.onclick);
primaryButton.dataset.category = 'dismiss';
buttonSpacer.insertAdjacentElement('afterend', primaryButton);
break;
case this.panelTypeError:
this.setAttribute('indicator', 'status');
this.setAttribute('status', 'failure');
this.primaryText = str('FILE_ERROR_GENERIC');
this.secondaryText = '';
secondaryButton = document.createElement('xf-button');
secondaryButton.id = 'secondary-action';
secondaryButton.onclick = assert(this.onclick);
secondaryButton.dataset.category = 'dismiss';
buttonSpacer.insertAdjacentElement('afterend', secondaryButton);
break;
case this.panelTypeInfo:
break;
case this.panelTypeFormatProgress:
this.setAttribute('indicator', 'status');
this.setAttribute('status', 'hard-drive');
break;
case this.panelTypeSyncProgress:
this.setAttribute('indicator', 'progress');
break;
}
this.panelType_ = type;
}
/**
* Registers this instance to listen to these attribute changes.
* @private
*/
static get observedAttributes() {
return [
'count',
'errormark',
'indicator',
'panel-type',
'primary-text',
'progress',
'secondary-text',
'status',
];
}
/**
* Callback triggered by the browser when our attribute values change.
* @param {string} name Attribute that's changed.
* @param {?string} oldValue Old value of the attribute.
* @param {?string} newValue New value of the attribute.
* @private
*/
attributeChangedCallback(name, oldValue, newValue) {
/** @type {Element} */
let indicator = null;
/** @type {Element} */
let textNode;
// TODO(adanilo) Chop out each attribute handler into a function.
switch (name) {
case 'count':
if (this.indicator_) {
this.indicator_.setAttribute('label', newValue || '');
}
break;
case 'errormark':
if (this.indicator_) {
this.indicator_.setAttribute('errormark', newValue || '');
}
break;
case 'indicator':
// Get rid of any existing indicator
const oldIndicator = this.shadowRoot.querySelector('#indicator');
if (oldIndicator) {
oldIndicator.remove();
}
switch (newValue) {
case 'progress':
case 'largeprogress':
indicator = document.createElement('xf-circular-progress');
if (newValue === 'largeprogress') {
indicator.setAttribute('radius', '14');
} else {
indicator.setAttribute('radius', '10');
}
break;
case 'status':
indicator = document.createElement('iron-icon');
const status = this.getAttribute('status');
if (status) {
indicator.setAttribute('icon', `files36:${status}`);
}
break;
}
this.indicator_ = indicator;
if (indicator) {
const itemRoot = this.shadowRoot.querySelector('.xf-panel-item');
indicator.setAttribute('id', 'indicator');
itemRoot.prepend(indicator);
}
break;
case 'panel-type':
this.setPanelType(Number(newValue));
if (this.parent && this.parent.updateSummaryPanel) {
this.parent.updateSummaryPanel();
}
break;
case 'progress':
if (this.indicator_) {
this.indicator_.progress = Number(newValue);
if (this.parent && this.parent.updateProgress) {
this.parent.updateProgress();
}
}
break;
case 'status':
if (this.indicator_) {
this.indicator_.setAttribute('icon', `files36:${newValue}`);
}
break;
case 'primary-text':
textNode = this.shadowRoot.querySelector('.xf-panel-label-text');
if (textNode) {
textNode.textContent = newValue;
// Set the aria labels for the activity and cancel button.
this.setAttribute('aria-label', /** @type {string} */ (newValue));
}
break;
case 'secondary-text':
textNode = this.shadowRoot.querySelector('.xf-panel-secondary-text');
if (!textNode) {
const parent = this.shadowRoot.querySelector('.xf-panel-text');
if (!parent) {
return;
}
textNode = document.createElement('span');
textNode.setAttribute('class', 'xf-panel-secondary-text');
parent.appendChild(textNode);
}
// Remove the secondary text node if the text is empty
if (newValue == '') {
textNode.remove();
} else {
textNode.textContent = newValue;
}
break;
}
}
/**
* DOM connected.
* @private
*/
connectedCallback() {
this.onclick = this.onClicked_.bind(this);
// Set click event handler references.
let button = this.shadowRoot.querySelector('#primary-action');
if (button) {
button.onclick = this.onclick;
}
button = this.shadowRoot.querySelector('#secondary-action');
if (button) {
button.onclick = this.onclick;
}
}
/**
* DOM disconnected.
* @private
*/
disconnectedCallback() {
// Replace references to any signal callback.
this.signal_ = console.log;
// Clear click event handler references.
let button = this.shadowRoot.querySelector('#primary-action');
if (button) {
button.onclick = null;
}
button = this.shadowRoot.querySelector('#secondary-action');
if (button) {
button.onclick = null;
}
this.onclick = null;
}
/**
* Handles 'click' events from our sub-elements and sends
* signals to the |signal_| callback if needed.
* @param {?Event} event
* @private
*/
onClicked_(event) {
event.stopImmediatePropagation();
event.preventDefault();
// Ignore clicks on the panel item itself.
if (event.target === this) {
return;
}
const id = assert(event.target.dataset.category);
this.signal_(id);
}
/**
* Sets the callback that triggers signals from events on the panel.
* @param {?function(*)} signal
*/
set signalCallback(signal) {
this.signal_ = signal || console.log;
}
/**
* Set the visibility of the error marker.
* @param {string} visibility Visibility value being set.
*/
set errorMarkerVisibility(visibility) {
this.setAttribute('errormark', visibility);
}
/**
* Getter for the visibility of the error marker.
*/
get errorMarkerVisibility() {
// If we have an indicator on the panel, then grab the
// visibility value from that.
if (this.indicator_) {
return this.indicator_.errorMarkerVisibility;
}
// If there's no indicator on the panel just return the
// value of any attribute as a fallback.
return this.getAttribute('errormark');
}
/**
* Setter to set the indicator type.
* @param {string} indicator Progress (optionally large) or status.
*/
set indicator(indicator) {
this.setAttribute('indicator', indicator);
}
/**
* Getter for the progress indicator.
*/
get indicator() {
return this.getAttribute('indicator');
}
/**
* Setter to set the success/failure indication.
* @param {string} status Status value being set.
*/
set status(status) {
this.setAttribute('status', status);
}
/**
* Getter for the success/failure indication.
*/
get status() {
return this.getAttribute('status');
}
/**
* Setter to set the progress property, sent to any child indicator.
* @param {string} progress Progress value being set.
* @public
*/
set progress(progress) {
this.setAttribute('progress', progress);
}
/**
* Getter for the progress indicator percentage.
*/
get progress() {
return this.indicator_.progress || 0;
}
/**
* Setter to set the primary text on the panel.
* @param {string} text Text to be shown.
*/
set primaryText(text) {
this.setAttribute('primary-text', text);
}
/**
* Getter for the primary text on the panel.
* @return {string}
*/
get primaryText() {
return this.getAttribute('primary-text');
}
/**
* Setter to set the secondary text on the panel.
* @param {string} text Text to be shown.
*/
set secondaryText(text) {
this.setAttribute('secondary-text', text);
}
/**
* Getter for the secondary text on the panel.
* @return {string}
*/
get secondaryText() {
return this.getAttribute('secondary-text');
}
/**
* Setter to set the panel type.
* @param {number} type Enum value for the panel type.
*/
set panelType(type) {
this.setAttribute('panel-type', type);
}
/**
* Getter for the panel type.
* TODO(crbug.com/947388) Add closure annotations to getters.
*/
get panelType() {
return this.panelType_;
}
/**
* Getter for the primary action button.
*/
get primaryButton() {
return this.shadowRoot.querySelector('#primary-action');
}
/**
* Getter for the secondary action button.
*/
get secondaryButton() {
return this.shadowRoot.querySelector('#secondary-action');
}
/**
* Getter for the panel text div.
*/
get textDiv() {
return this.shadowRoot.querySelector('.xf-panel-text');
}
/**
* Setter to replace the default aria-label on any close button.
* @param {string} text Text to set for the 'aria-label'.
*/
set closeButtonAriaLabel(text) {
const action = this.shadowRoot.querySelector('#secondary-action');
if (action && action.dataset.category === 'cancel') {
action.setAttribute('aria-label', text);
}
}
}
window.customElements.define('xf-panel-item', PanelItem);
//# sourceURL=//ui/file_manager/file_manager/foreground/elements/xf_panel_item.js