Skip to content

Commit 118c6dd

Browse files
authored
Update v1.1.0
1 parent b42d9a7 commit 118c6dd

File tree

1 file changed

+183
-38
lines changed

1 file changed

+183
-38
lines changed

countdown_mod_card.js

Lines changed: 183 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//v1.1.0
12
import {
23
LitElement,
34
css,
@@ -11,10 +12,7 @@ import {
1112
} from "https://unpkg.com/[email protected]/lit-html.js?module";
1213

1314

14-
class CountdownModCard extends LitElement {
15-
16-
// ... (大部分JS代码与 v3.1.0 相同) ...
17-
15+
class CountdownModCard extends LitElement {
1816
_handleInteractionStart(e, type) {
1917
if (this._isSliding) return;
2018
e.preventDefault();
@@ -46,7 +44,6 @@ class CountdownModCard extends LitElement {
4644
window.addEventListener(isTouch ? 'touchend' : 'mouseup', this._handleInteractionEnd, eventOptions);
4745
}
4846

49-
// ... (其余所有代码,包括 constructor, render, styles 等都保持不变) ...
5047

5148
// <editor-fold desc="Unchanged Code - Collapsed for Readability">
5249
static _getTemplates() {
@@ -175,33 +172,112 @@ class CountdownModCard extends LitElement {
175172
}
176173

177174
_evaluateTemplate(value) {
175+
// 配置还没处理好,或者不是字符串,直接原样返回
178176
if (!this._configProcessed) return value;
179-
if (typeof value !== 'string') return value;
177+
if (typeof value !== "string") return value;
178+
180179
const trimmedValue = value.trim();
181-
if (!trimmedValue.startsWith('[[[') || !trimmedValue.endsWith(']]]')) return value;
180+
// 不是 [[[ ]]] 模板,直接返回
181+
if (!trimmedValue.startsWith('[[[') || !trimmedValue.endsWith(']]]')) {
182+
return value;
183+
}
184+
182185
try {
186+
// 去掉开头的 [[[ 和结尾的 ]]]
183187
const template = trimmedValue.substring(3, trimmedValue.length - 3);
184188
const entityState = this.hass.states[this.config.timer_entity];
189+
190+
// 先处理 variables
185191
const rawVariables = this.config.variables || {};
186192
const evaluatedVariables = {};
193+
187194
for (const key in rawVariables) {
188-
if (typeof rawVariables[key] === 'string' && rawVariables[key].trim().startsWith('[[[')) {
189-
const varTemplate = rawVariables[key].substring(3, rawVariables[key].length - 3);
190-
const varFunc = new Function('states', 'entity', 'user', 'hass', 'config', varTemplate);
191-
evaluatedVariables[key] = varFunc(this.hass.states, entityState, this.hass.user, this.hass, this.config);
195+
const raw = rawVariables[key];
196+
197+
if (typeof raw === "string") {
198+
const trimmedVar = raw.trim();
199+
200+
// 只有在 [[[ ]]] 包裹时才当成模板执行
201+
if (trimmedVar.startsWith('[[[') && trimmedVar.endsWith(']]]')) {
202+
const varTemplate = trimmedVar.substring(3, trimmedVar.length - 3);
203+
try {
204+
// 变量也可以互相引用,所以把 variables 也一起传进去
205+
const varFunc = new Function(
206+
"states",
207+
"entity",
208+
"user",
209+
"hass",
210+
"config",
211+
"variables",
212+
varTemplate
213+
);
214+
evaluatedVariables[key] = varFunc(
215+
this.hass.states,
216+
entityState,
217+
this.hass.user,
218+
this.hass,
219+
this.config,
220+
evaluatedVariables
221+
);
222+
} catch (err) {
223+
console.error(
224+
"Error evaluating variable template:",
225+
key,
226+
err,
227+
"Template:",
228+
raw
229+
);
230+
evaluatedVariables[key] = null;
231+
}
232+
} else {
233+
// 普通字符串,直接用
234+
evaluatedVariables[key] = raw;
235+
}
192236
} else {
193-
evaluatedVariables[key] = rawVariables[key];
237+
// 非字符串类型,原样透传
238+
evaluatedVariables[key] = raw;
194239
}
195240
}
196-
const func = new Function('states', 'entity', 'user', 'hass', 'config', 'variables', template);
197-
return func(this.hass.states, entityState, this.hass.user, this.hass, this.config, evaluatedVariables);
241+
242+
// 最终模板执行,把 variables 也带进去
243+
const func = new Function(
244+
"states",
245+
"entity",
246+
"user",
247+
"hass",
248+
"config",
249+
"variables",
250+
template
251+
);
252+
return func(
253+
this.hass.states,
254+
entityState,
255+
this.hass.user,
256+
this.hass,
257+
this.config,
258+
evaluatedVariables
259+
);
198260
} catch (e) {
199261
console.error("Error evaluating template:", e, "Template:", value);
200-
return `TEMPLATE_ERROR`;
262+
return "TEMPLATE_ERROR";
201263
}
202264
}
203-
204-
_computeStyles(e){if(!this._configProcessed || !this.config.styles||!this.config.styles[e])return"";return this.config.styles[e].map((t=>{const s=Object.keys(t)[0],i=this._evaluateTemplate(t[s]);return`${s}: ${i};`})).join("")}
265+
266+
267+
268+
_computeStyles(key) {
269+
if (!this._configProcessed || !this.config.styles || !this.config.styles[key])
270+
return "";
271+
272+
return this.config.styles[key]
273+
.map((obj) => {
274+
const cssKey = Object.keys(obj)[0];
275+
const rawVal = obj[cssKey];
276+
const val = this._evaluateTemplate(rawVal);
277+
return `${cssKey}: ${val};`;
278+
})
279+
.join(" ");
280+
}
205281

206282
_startFromIdle() {
207283
this._seconds = 0;
@@ -321,38 +397,107 @@ class CountdownModCard extends LitElement {
321397
}
322398

323399
render() {
324-
if (!this._configProcessed) { return cardHtml``; }
400+
if (!this._configProcessed) {
401+
return cardHtml``;
402+
}
403+
325404
const entityState = this.hass.states[this.config.timer_entity];
326-
const isTimerActive = entityState && entityState.state === 'active';
405+
const isTimerActive = entityState && entityState.state === "active";
406+
327407
const isStartDisabled = (this._hours * 3600 + this._minutes * 60) === 0;
408+
328409
let part1, part2;
329-
const [h, m, s] = this._remainingTime.split(':').map(Number);
410+
411+
const [h, m, s] = this._remainingTime.split(":").map(Number);
330412
const totalRemainingSeconds = h * 3600 + m * 60 + s;
413+
331414
if (this._isSliding) {
332-
part1 = String(this._slidingType === 'hours' ? this._slidingCurrentValue : this._hours).padStart(2, '0');
333-
part2 = String(this._slidingType === 'minutes' ? this._slidingCurrentValue : this._minutes).padStart(2, '0');
415+
// 正在滑动时,用滑动中的值
416+
part1 = String(
417+
this._slidingType === "hours" ? this._slidingCurrentValue : this._hours
418+
).padStart(2, "0");
419+
part2 = String(
420+
this._slidingType === "minutes" ? this._slidingCurrentValue : this._minutes
421+
).padStart(2, "0");
334422
} else if (isTimerActive) {
335-
if (totalRemainingSeconds > 0 && totalRemainingSeconds < 60 && !this.config.always_show_minutes) { part1 = String(m).padStart(2, '0'); part2 = String(s).padStart(2, '0'); } else { part1 = String(h).padStart(2, '0'); part2 = String(m).padStart(2, '0'); }
336-
} else { part1 = String(this._hours).padStart(2, '0'); part2 = String(this._minutes).padStart(2, '0'); }
423+
// 计时器运行中,可能显示 mm:ss 或 hh:mm
424+
if (
425+
totalRemainingSeconds > 0 &&
426+
totalRemainingSeconds < 60 &&
427+
!this.config.always_show_minutes
428+
) {
429+
part1 = String(m).padStart(2, "0");
430+
part2 = String(s).padStart(2, "0");
431+
} else {
432+
part1 = String(h).padStart(2, "0");
433+
part2 = String(m).padStart(2, "0");
434+
}
435+
} else {
436+
// 空闲时显示配置好的 hours / minutes
437+
part1 = String(this._hours).padStart(2, "0");
438+
part2 = String(this._minutes).padStart(2, "0");
439+
}
440+
337441
const currentIcon = isTimerActive ? this.config.stop_icon : this.config.start_icon;
338442
let buttonContent;
339-
if (currentIcon) { const evaluatedIcon = this._evaluateTemplate(currentIcon); if (evaluatedIcon.startsWith('mdi:')) { buttonContent = cardHtml`<ha-icon .icon=${evaluatedIcon}></ha-icon>`; } else { buttonContent = cardHtml`<img src=${evaluatedIcon} />`; } } else { buttonContent = isTimerActive ? '停用' : '开始'; }
443+
444+
if (currentIcon) {
445+
const evaluatedIcon = this._evaluateTemplate(currentIcon);
446+
if (typeof evaluatedIcon === "string" && evaluatedIcon.startsWith("mdi:")) {
447+
buttonContent = cardHtml`<ha-icon .icon=${evaluatedIcon}></ha-icon>`;
448+
} else {
449+
buttonContent = cardHtml`<img src=${evaluatedIcon} />`;
450+
}
451+
} else {
452+
buttonContent = isTimerActive ? "停用" : "开始";
453+
}
454+
340455
return cardHtml`
341-
<ha-card style=${this._computeStyles('card')}>
342-
<div class="main-container" style=${this._computeStyles('grid')}>
343-
${this.config.title ? cardHtml`<div class="title" style=${this._computeStyles('title')}>${this._evaluateTemplate(this.config.title)}</div>` : ''}
456+
<ha-card style=${this._computeStyles("card")}>
457+
<div class="main-container" style=${this._computeStyles("grid")}>
458+
${this.config.title
459+
? cardHtml`<div class="title" style=${this._computeStyles("title")}>
460+
${this._evaluateTemplate(this.config.title)}
461+
</div>`
462+
: ""}
463+
344464
<div class="time-display">
345-
<div class="time-setter ${isTimerActive ? 'active' : ''}" style=${this._computeStyles('timer')}>
346-
<div class="time-part ${this._isSliding && this._slidingType === 'hours' ? 'sliding' : ''}" @mousedown="${(e) => this._handleInteractionStart(e, 'hours')}" @touchstart="${(e) => this._handleInteractionStart(e, 'hours')}">${part1}</div>
347-
<div class="colon ${isTimerActive ? 'blinking' : ''}">:</div>
348-
<div class="time-part ${this._isSliding && this._slidingType === 'minutes' ? 'sliding' : ''}" @mousedown="${(e) => this._handleInteractionStart(e, 'minutes')}" @touchstart="${(e) => this._handleInteractionStart(e, 'minutes')}">${part2}</div>
465+
<div
466+
class="time-setter ${isTimerActive ? "active" : ""}"
467+
style=${this._computeStyles("timer")}
468+
>
469+
<div
470+
class="time-part ${this._isSliding && this._slidingType === "hours"
471+
? "sliding"
472+
: ""}"
473+
style=${this._computeStyles("timer_part")}
474+
@mousedown="${(e) => this._handleInteractionStart(e, "hours")}"
475+
@touchstart="${(e) => this._handleInteractionStart(e, "hours")}"
476+
>
477+
${part1}
478+
</div>
479+
480+
<div class="colon ${isTimerActive ? "blinking" : ""}">:</div>
481+
482+
<div
483+
class="time-part ${this._isSliding && this._slidingType === "minutes"
484+
? "sliding"
485+
: ""}"
486+
style=${this._computeStyles("timer_part")}
487+
@mousedown="${(e) => this._handleInteractionStart(e, "minutes")}"
488+
@touchstart="${(e) => this._handleInteractionStart(e, "minutes")}"
489+
>
490+
${part2}
491+
</div>
349492
</div>
350493
</div>
351-
<button
352-
class="action-button ${isTimerActive ? 'stop' : 'start'}"
353-
style=${this._computeStyles('button')}
354-
@click="${isTimerActive ? this._handleStop : this._startFromIdle}"
355-
?disabled="${!isTimerActive && isStartDisabled}">
494+
495+
<button
496+
class="action-button ${isTimerActive ? "stop" : "start"}"
497+
style=${this._computeStyles("button")}
498+
@click="${isTimerActive ? this._handleStop : this._startFromIdle}"
499+
?disabled="${!isTimerActive && isStartDisabled}"
500+
>
356501
${buttonContent}
357502
</button>
358503
</div>
@@ -431,4 +576,4 @@ class CountdownModCard extends LitElement {
431576

432577
customElements.define('countdown-mod-card', CountdownModCard);
433578
window.customCards = window.customCards || [];
434-
window.customCards.push({ type: "countdown-mod-card", name: "Countdown Mod Card v1.0.1", description: "一个紧凑型倒计时卡片", preview: true });
579+
window.customCards.push({ type: "countdown-mod-card", name: "Countdown Mod Card v1.1.0", description: "一款为 Home Assistant Lovelace 设计的现代化、紧凑型倒计时卡片", preview: true });

0 commit comments

Comments
 (0)