blob: 044f5689cb243a7169f020b1d21c15ff388cf97a [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 {assert} from 'chrome://resources/js/assert.m.js';
/**
* Definition of a circular progress indicator custom element.
* The element supports two attributes for control - 'radius' and 'progress'.
* Default radius for the element is 10px, use the DOM API
* element.setAttribute('radius', '12px'); to set the radius to 12px;
* Default progress is 0, progress is a value from 0 to 100, use
* element.setAttribute('progress', '50'); to set progress to half complete
* or alternately, set the 'element.progress' JS property for the same result.
*/
export class CircularProgress extends HTMLElement {
constructor() {
super();
const host = document.createElement('template');
host.innerHTML = this.constructor.template_;
this.attachShadow({mode: 'open'}).appendChild(host.content.cloneNode(true));
/** @private {number} */
this.progress_ = 0.0;
/**
* The visual indicator for the progress is accomplished by changing the
* stroke-dasharray SVG attribute on the top circle. The stroke-dasharray
* is calculated by using the circumference of the circle as the 100%
* length and then setting the dash length to match the percentage of
* the set 'progress_' value.
* @private {Element}
*/
this.indicator_ = this.shadowRoot.querySelector('.top');
/** @private {Element} */
this.errormark_ = assert(this.shadowRoot.querySelector('.errormark'));
/** @private {Element} */
this.label_ = this.shadowRoot.querySelector('.label');
/** @private {number} */
this.maxProgress_ = 100.0;
/**
* The circumference for the circle (default 63 for radius r='10').
* @private {number}
*/
this.fullCircle_ = 63;
}
/**
* Static getter for the custom element template.
* @private
*/
static get template_() {
return `<style>
.progress {
height: 36px;
width: 36px;
}
:host-context([detailed-panel][data-category='expanded'])
.progress {
height: 32px;
width: 32px;
}
:host-context([detailed-panel][data-category='collapsed'])
.progress {
height: 28px;
width: 28px;
}
.bottom {
stroke: var(--google-blue-100);
fill: none;
}
.top {
stroke: var(--google-blue-600);
stroke-linecap: round;
fill: none;
}
text {
font: bold 14px Roboto;
fill: var(--google-blue-600);
}
</style>
<div class='progress'>
<svg xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 36 36'>
<g id='circles' stroke-width='3'>
<circle class='bottom' cx='18' cy='18' r='10'/>
<circle class='top' transform='rotate(-90 18 18)'
cx='18' cy='18' r='10' stroke-dasharray='0 1'/>
</g>
<text class='label' x='18' y='18' text-anchor='middle'
alignment-baseline='central'></text>
<circle class='errormark' visibility='hidden'
cx='25.5' cy='10.5' r='4'
fill='#D93025' stroke='none'/>
</svg>
</div>`;
}
/**
* Registers this instance to listen to these attribute changes.
* @private
*/
static get observedAttributes() {
return [
'errormark',
'label',
'progress',
'radius',
];
}
/**
* Sets the indicators progress position.
* @param {number} progress A value between 0 and maxProgress_ to indicate.
* @return {number}
* @public
*/
setProgress(progress) {
// Clamp progress to 0 .. maxProgress_.
progress = Math.min(Math.max(progress, 0), this.maxProgress_);
const value = (progress / this.maxProgress_) * this.fullCircle_;
this.indicator_.setAttribute(
'stroke-dasharray', value + ' ' + this.fullCircle_);
return progress;
}
/**
* Sets the position of the error indicator.
* The error indicator is used by the summary panel. Its position is aligned
* with the top-right square that contains the progress circle itself.
* @param {number} radius The radius of the progress circle.
* @param {number} strokeWidth The width of the progress circle stroke.
* @private
*/
setErrorPosition_(radius, strokeWidth) {
const center = 18;
const x = center + radius + (strokeWidth / 2) - 4;
const y = center - radius - (strokeWidth / 2) + 4;
this.errormark_.setAttribute('cx', x);
this.errormark_.setAttribute('cy', y);
}
/**
* Callback triggered by the browser when our attribute values change.
* TODO(crbug.com/947388) Add unit tests to exercise attribute edge cases.
* @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) {
if (oldValue === newValue) {
return;
}
switch (name) {
case 'errormark':
this.errormark_.setAttribute('visibility', newValue || '');
break;
case 'label':
this.label_.textContent = newValue;
break;
case 'radius':
if (!newValue) {
break;
}
const radius = Number(newValue);
// Restrict the allowed size to what fits in our area.
if (radius < 0 || radius > 16.5) {
return;
}
let strokeWidth = 3;
if (radius > 10) {
const circles = this.shadowRoot.querySelector('#circles');
circles.setAttribute('stroke-width', '4');
strokeWidth = 4;
}
// Position the error indicator relative to the progress circle.
this.setErrorPosition_(radius, strokeWidth);
// Calculate the circumference for the progress dash length.
this.fullCircle_ = Math.PI * 2 * radius;
const bottom = this.shadowRoot.querySelector('.bottom');
bottom.setAttribute('r', radius.toString());
this.indicator_.setAttribute('r', radius.toString());
this.setProgress(this.progress_);
break;
case 'progress':
const progress = Number(newValue);
this.progress_ = this.setProgress(progress);
break;
}
}
/**
* Getter for the visibility of the error marker.
* @public
* @return {string}
*/
get errorMarkerVisibility() {
return this.errormark_.getAttribute('visibility');
}
/**
* Set the visibility of the error marker.
* @param {string} visibility Visibility value being set.
* @public
*/
set errorMarkerVisibility(visibility) {
// Reflect the progress property into the attribute.
this.setAttribute('errormark', visibility);
}
/**
* Getter for the current state of the progress indication.
* @public
* @return {string}
*/
get progress() {
return this.progress_.toString();
}
/**
* Sets the progress position between 0 and 100.0.
* @param {string} progress Progress value being set.
* @public
*/
set progress(progress) {
// Reflect the progress property into the attribute.
this.setAttribute('progress', progress);
}
/**
* Set the text label in the centre of the progress indicator.
* This is used to indicate multiple operations in progress.
* @param {string} label Text to place inside the circle.
* @public
*/
set label(label) {
this.setAttribute('label', label);
}
}
window.customElements.define('xf-circular-progress', CircularProgress);
//# sourceURL=//ui/file_manager/file_manager/foreground/elements/xf_circular_progress.js