456 files

This commit is contained in:
Home Assistant Version Control
2026-10-01 05:38:21 +00:00
parent 4192ecbaae
commit 5d6d845ebf
456 changed files with 64951 additions and 3828 deletions
@@ -23,6 +23,16 @@ const css = LitElement.prototype.css;
const tmClaimList = (v) => (Array.isArray(v) ? v : []);
const _safeColor = (c, d) => (typeof c === "string" && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : d);
// Keeps number runs like "3 / 1" reading left to right in RTL text (#995);
// identity until the design layer has loaded (it is also what stamps dir).
const _ltrNums = (s) => (window.__taskmate_design && window.__taskmate_design.ltrNums ? window.__taskmate_design.ltrNums(s) : s);
// "a / b" for a template: the same text nodes as a literal `${a} / ${b}` in
// LTR (so it renders pixel-for-pixel as before), the isolated string in RTL.
const _ltrRatio = (a, b) => {
const s = `${a} / ${b}`;
const r = _ltrNums(s);
return r === s ? html`${a} / ${b}` : r;
};
class TaskMateRewardsCard extends LitElement {
static get properties() {
@@ -287,7 +297,7 @@ class TaskMateRewardsCard extends LitElement {
content: '';
position: absolute;
top: 0;
left: -100%;
inset-inline-start: -100%;
width: 50%;
height: 100%;
background: linear-gradient(
@@ -300,8 +310,8 @@ class TaskMateRewardsCard extends LitElement {
}
@keyframes shimmer {
0% { left: -100%; }
100% { left: 200%; }
0% { inset-inline-start: -100%; }
100% { inset-inline-start: 200%; }
}
.progress-text {
@@ -310,7 +320,7 @@ class TaskMateRewardsCard extends LitElement {
color: var(--text-secondary);
white-space: nowrap;
min-width: 0;
text-align: right;
text-align: end;
}
/* Jackpot reward styles */
@@ -325,8 +335,8 @@ class TaskMateRewardsCard extends LitElement {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
background: linear-gradient(
45deg,
@@ -393,7 +403,7 @@ class TaskMateRewardsCard extends LitElement {
content: '';
position: absolute;
top: 0;
left: -100%;
inset-inline-start: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
@@ -407,21 +417,21 @@ class TaskMateRewardsCard extends LitElement {
/* Shimmer 3 times then wait ~30s before repeating */
@keyframes jackpot-segment-shimmer {
0% { left: -100%; }
2.5% { left: 200%; }
5% { left: -100%; }
7.5% { left: 200%; }
10% { left: -100%; }
12.5% { left: 200%; }
15%, 100% { left: -100%; opacity: 0; }
0% { inset-inline-start: -100%; }
2.5% { inset-inline-start: 200%; }
5% { inset-inline-start: -100%; }
7.5% { inset-inline-start: 200%; }
10% { inset-inline-start: -100%; }
12.5% { inset-inline-start: 200%; }
15%, 100% { inset-inline-start: -100%; opacity: 0; }
}
.jackpot-segment:first-child {
border-radius: 9px 0 0 9px;
border-start-start-radius: 9px; border-start-end-radius: 0; border-end-end-radius: 0; border-end-start-radius: 9px;
}
.jackpot-segment:last-child {
border-radius: 0 9px 9px 0;
border-start-start-radius: 0; border-start-end-radius: 9px; border-end-end-radius: 9px; border-end-start-radius: 0;
}
.jackpot-segment:only-child {
@@ -546,7 +556,21 @@ class TaskMateRewardsCard extends LitElement {
/* Pending approval state */
.reward-row.pending-approval {
opacity: 0.6;
border-left: 3px solid #e67e22;
border-inline-start: 3px solid #e67e22;
}
/* Reject reason (#976) */
.reject-note {
margin-top: 6px;
padding: 4px 8px;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
line-height: 1.3;
white-space: normal;
overflow-wrap: anywhere;
background: rgba(244, 67, 54, 0.12);
color: var(--error-color, #d32f2f);
}
.pending-label {
@@ -747,14 +771,14 @@ class TaskMateRewardsCard extends LitElement {
font-weight: 700;
font-size: 1.15rem;
color: var(--text-primary);
margin-left: auto;
margin-inline-start: auto;
}
.spendable-banner .spendable-of {
font-weight: 400;
color: var(--text-secondary);
font-size: 0.82rem;
margin-left: 4px;
margin-inline-start: 4px;
}
/* Pool Mode controls — render BELOW the progress bar, full width */
@@ -801,7 +825,7 @@ class TaskMateRewardsCard extends LitElement {
display: flex;
align-items: stretch;
gap: 6px;
margin-left: auto;
margin-inline-start: auto;
flex: 0 0 auto;
}
.deposit-custom input {
@@ -941,7 +965,7 @@ class TaskMateRewardsCard extends LitElement {
flex-direction: column;
gap: 6px;
font-size: 0.9rem;
text-align: left;
text-align: start;
}
.dialog-points-row {
display: flex;
@@ -995,6 +1019,12 @@ class TaskMateRewardsCard extends LitElement {
.rw-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.rw-name { font-weight: 800; font-size: 15px; }
.rw-desc { font-size: 12px; }
.rw-reject {
margin: 2px 0 6px; padding: 4px 8px; border-radius: 8px;
font-size: 12px; font-weight: 700; line-height: 1.3;
white-space: normal; overflow-wrap: anywhere;
background: color-mix(in srgb, var(--tmd-bad, #e74c3c) 14%, transparent); color: var(--tmd-bad, #e74c3c);
}
.rw-avail { align-self: flex-start; font-size: 11px; }
.rw-avail-warn { background: color-mix(in srgb, var(--tmd-warn) 16%, transparent);
color: var(--tmd-warn); }
@@ -1010,7 +1040,7 @@ class TaskMateRewardsCard extends LitElement {
.rw-status { font-size: 12px; }
.rw-status-good { color: var(--tmd-good); font-weight: 800; }
.rw-pool-btns { gap: 6px; flex-wrap: wrap; align-items: center; }
.rw-pool-btns .deposit-custom { margin-left: auto; gap: 6px; }
.rw-pool-btns .deposit-custom { margin-inline-start: auto; gap: 6px; }
.rw-deposit-input {
width: 70px;
min-width: 0;
@@ -1047,6 +1077,9 @@ class TaskMateRewardsCard extends LitElement {
header_color: '#e67e22',
...config,
};
// Dashboards saved before #1010 stored the English default title; treat it
// as unset so the translated default is shown instead.
if (this.config.title === "Rewards") this.config.title = "";
}
getCardSize() {
@@ -1060,7 +1093,6 @@ class TaskMateRewardsCard extends LitElement {
static getStubConfig() {
return {
entity: "sensor.taskmate_overview",
title: "Rewards",
child_id: null,
show_child_badges: true,
enable_pool_mode: false,
@@ -1287,8 +1319,10 @@ class TaskMateRewardsCard extends LitElement {
<ha-icon icon="${pointsIcon}"></ha-icon>
<span class="spendable-label">${this._t('rewards.spendable_balance')}</span>
<span class="spendable-value">
${spendable}
${detail}
${spendable !== gross && this.getAttribute("dir") === "rtl"
// "12 / 40" spans two elements, so isolate it with markup (#995).
? html`<bdi dir="ltr">${spendable} ${detail}</bdi>`
: html`${spendable} ${detail}`}
</span>
</div>
`;
@@ -1437,7 +1471,7 @@ class TaskMateRewardsCard extends LitElement {
<ha-icon icon="mdi:clock-outline"></ha-icon>
${this._t('rewards.awaiting_approval')}
</div>
` : ''}
` : this._renderRejectNote(reward, childId ? relevantChild : null, 'reject-note')}
${showChildBadges && !isJackpot
? html`
@@ -1472,6 +1506,17 @@ class TaskMateRewardsCard extends LitElement {
`;
}
/**
* Reject reasons (#976): why a parent turned down this child's last claim.
* Only with a child in context — the reason is personal. Called from the
* classic row AND _designRewardRow, so every design shows it.
*/
_renderRejectNote(reward, child, cls) {
const rej = ((child && child.rejections) || []).find(r => r.kind === "reward" && r.id === reward.id);
if (!rej || !rej.reason) return '';
return html`<div class="${cls}" role="note">↩️ ${this._t('rewards.rejected_note', { reason: rej.reason })}</div>`;
}
/** "Available Fri, Sat · 18:00–21:00" for a time-locked reward (#857). */
_timeLockLabel(reward) {
const summary = window.__taskmate_time_lock_label
@@ -2060,7 +2105,7 @@ class TaskMateRewardsCard extends LitElement {
? html`<span class="muted rw-status">⏳ ${this._t('rewards.awaiting_approval')}</span>`
: d.canAfford && d.showClaim
? html`<span class="rw-status rw-status-good">${this._t('reward_progress.ready_to_claim')}</span>`
: html`<span class="muted rw-status">${d.currentStars} / ${d.displayCost}</span>`;
: html`<span class="muted rw-status">${_ltrRatio(d.currentStars, d.displayCost)}</span>`;
return html`
<div class="rw-card ${d.isJackpot ? 'rw-jackpot' : ''} ${d.isUnavailable || d.isTimeLocked ? 'rw-unavail' : ''}"
@@ -2076,6 +2121,7 @@ class TaskMateRewardsCard extends LitElement {
</div>
${costBadge}
</div>
${d.hasPendingClaim ? '' : this._renderRejectNote(reward, d.childId ? d.relevantChild : null, 'rw-reject')}
${bar}
${d.isJackpot ? html`
<div class="row rw-pool-foot">
@@ -2231,7 +2277,7 @@ class TaskMateRewardsCardEditor extends LitElement {
border-radius: 4px;
padding: 4px 10px;
cursor: pointer;
margin-left: auto;
margin-inline-start: auto;
}
.colour-helper {
color: var(--secondary-text-color);