blob: b4fa4a8e953385ac8aed229ec7a387b2c2248308 [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@polymer/iron-selector/iron-selector';
import '../../shared/gr-button/gr-button';
import '../../../styles/gr-voting-styles';
import '../../../styles/shared-styles';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-label-score-row_html';
import {customElement, property} from '@polymer/decorators';
import {IronSelectorElement} from '@polymer/iron-selector/iron-selector';
import {
LabelNameToValueMap,
LabelNameToInfoMap,
QuickLabelInfo,
DetailedLabelInfo,
} from '../../../types/common';
import {hasOwnProperty} from '../../../utils/common-util';
export interface Label {
name: string;
value: string | null;
}
// TODO(TS): add description to explain what this is after moving
// gr-label-scores to ts
export interface LabelValuesMap {
[key: number]: number;
}
export interface GrLabelScoreRow {
$: {
labelSelector: IronSelectorElement;
};
}
declare global {
interface HTMLElementTagNameMap {
'gr-label-score-row': GrLabelScoreRow;
}
}
@customElement('gr-label-score-row')
export class GrLabelScoreRow extends PolymerElement {
static get template() {
return htmlTemplate;
}
/**
* Fired when any label is changed.
*
* @event labels-changed
*/
@property({type: Object})
label: Label | undefined | null;
@property({type: Object})
labels?: LabelNameToInfoMap;
@property({type: String, reflectToAttribute: true})
name?: string;
@property({type: Object})
permittedLabels: LabelNameToValueMap | undefined | null;
@property({type: Object})
labelValues?: LabelValuesMap;
@property({type: String})
_selectedValueText = 'No value selected';
@property({
computed: '_computePermittedLabelValues(permittedLabels, label.name)',
type: Array,
})
_items!: string[];
get selectedItem() {
if (!this._ironSelector) {
return undefined;
}
return this._ironSelector.selectedItem;
}
get selectedValue() {
if (!this._ironSelector) {
return undefined;
}
return this._ironSelector.selected;
}
setSelectedValue(value: string) {
// The selector may not be present if it’s not at the latest patch set.
if (!this._ironSelector) {
return;
}
this._ironSelector.select(value);
}
get _ironSelector() {
return this.$ && this.$.labelSelector;
}
_computeBlankItems(
permittedLabels: LabelNameToValueMap,
label: string,
side: string
) {
if (
!permittedLabels ||
!permittedLabels[label] ||
!permittedLabels[label].length ||
!this.labelValues ||
!Object.keys(this.labelValues).length
) {
return [];
}
const startPosition = this.labelValues[Number(permittedLabels[label][0])];
if (side === 'start') {
return new Array(startPosition);
}
const endPosition = this.labelValues[
Number(permittedLabels[label][permittedLabels[label].length - 1])
];
return new Array(Object.keys(this.labelValues).length - endPosition - 1);
}
_getLabelValue(
labels: LabelNameToInfoMap,
permittedLabels: LabelNameToValueMap,
label: Label
) {
if (label.value) {
return label.value;
} else if (
hasOwnProperty(labels[label.name], 'default_value') &&
hasOwnProperty(permittedLabels, label.name)
) {
// default_value is an int, convert it to string label, e.g. "+1".
return permittedLabels[label.name].find(
value =>
Number(value) === (labels[label.name] as QuickLabelInfo).default_value
);
}
return;
}
/**
* Maps the label value to exactly one of: min, max, positive, negative,
* neutral. Used for the 'vote' attribute, because we don't want to
* interfere with <iron-selector> using the 'class' attribute for setting
* 'iron-selected'.
*/
_computeVoteAttribute(value: number, index: number, totalItems: number) {
if (value < 0 && index === 0) {
return 'min';
} else if (value < 0) {
return 'negative';
} else if (value > 0 && index === totalItems - 1) {
return 'max';
} else if (value > 0) {
return 'positive';
} else {
return 'neutral';
}
}
_computeLabelValue(
labels?: LabelNameToInfoMap,
permittedLabels?: LabelNameToValueMap,
label?: Label
) {
// Polymer 2+ undefined check
if (
labels === undefined ||
permittedLabels === undefined ||
label === undefined
) {
return null;
}
if (!labels[label.name]) {
return null;
}
const labelValue = this._getLabelValue(labels, permittedLabels, label);
const len = permittedLabels[label.name]
? permittedLabels[label.name].length
: 0;
for (let i = 0; i < len; i++) {
const val = permittedLabels[label.name][i];
if (val === labelValue) {
return val;
}
}
return null;
}
_setSelectedValueText(e: Event) {
// Needed because when the selected item changes, it first changes to
// nothing and then to the new item.
const selectedItem = (e.target as IronSelectorElement)
.selectedItem as HTMLElement;
if (!selectedItem) {
return;
}
if (!this.$.labelSelector.items) {
return;
}
for (const item of this.$.labelSelector.items) {
if (selectedItem === item) {
item.setAttribute('aria-checked', 'true');
} else {
item.removeAttribute('aria-checked');
}
}
this._selectedValueText = selectedItem.getAttribute('title') || '';
// Needed to update the style of the selected button.
this.updateStyles();
const name = selectedItem.dataset['name'];
const value = selectedItem.dataset['value'];
this.dispatchEvent(
new CustomEvent('labels-changed', {
detail: {name, value},
bubbles: true,
composed: true,
})
);
}
_computeAnyPermittedLabelValues(
permittedLabels: LabelNameToValueMap,
labelName: string
) {
return (
permittedLabels &&
hasOwnProperty(permittedLabels, labelName) &&
permittedLabels[labelName].length
);
}
_computeHiddenClass(permittedLabels: LabelNameToValueMap, labelName: string) {
return !this._computeAnyPermittedLabelValues(permittedLabels, labelName)
? 'hidden'
: '';
}
_computePermittedLabelValues(
permittedLabels?: LabelNameToValueMap,
labelName?: string
) {
// Polymer 2: check for undefined
if (permittedLabels === undefined || labelName === undefined) {
return [];
}
return permittedLabels[labelName] || [];
}
_computeLabelValueTitle(
labels: LabelNameToInfoMap,
label: string,
value: string
) {
// TODO(TS): maybe add a type guard for DetailedLabelInfo and QuickLabelInfo
return (
labels[label] &&
(labels[label] as DetailedLabelInfo).values &&
(labels[label] as DetailedLabelInfo).values![value]
);
}
}