1+ //v1.1.0
12import {
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- <butto n
352- class= "action-button ${ isTimerActive ? 'stop' : 'start' } "
353- style = ${ this . _computeStyles ( 'button' ) }
354- @click = "${ isTimerActive ? this . _handleStop : this . _startFromIdle } "
355- ?dis abled= "${ ! isTimerActive && isStartDisabled } ">
494+
495+ <butto n
496+ class= "action-button ${ isTimerActive ? "stop" : "start" } "
497+ style = ${ this . _computeStyles ( "button" ) }
498+ @click = "${ isTimerActive ? this . _handleStop : this . _startFromIdle } "
499+ ?dis abled= "${ ! isTimerActive && isStartDisabled } "
500+ >
356501 ${ buttonContent }
357502 </ butto n>
358503 </ div>
@@ -431,4 +576,4 @@ class CountdownModCard extends LitElement {
431576
432577customElements . define ( 'countdown-mod-card' , CountdownModCard ) ;
433578window . 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