From 77e12d3e6974c2f87c4292fc2fefb9378f3cf28e Mon Sep 17 00:00:00 2001 From: David Date: Sat, 24 Aug 2024 08:15:21 -0400 Subject: [PATCH] v0.14.2 Release Candidate (#98) - Fix rendering issues on iPads and other legacy devices - Add visual editor --- dist/card_editor.js | 270 ++++++++++++++++++++++++++++++++++-- dist/const.js | 2 +- dist/ha-teamtracker-card.js | 4 +- dist/render_bye.js | 10 -- dist/render_in.js | 124 +++++------------ dist/render_not_found.js | 11 -- dist/render_post.js | 37 +---- dist/render_pre.js | 34 +---- dist/set_defaults.js | 21 +-- dist/set_sports.js | 23 +-- dist/styles.js | 64 +++++++++ dist/teamtracker_card.js | 17 ++- 12 files changed, 410 insertions(+), 207 deletions(-) create mode 100644 dist/styles.js diff --git a/dist/card_editor.js b/dist/card_editor.js index 758ac35..d0de7c9 100644 --- a/dist/card_editor.js +++ b/dist/card_editor.js @@ -4,20 +4,130 @@ import { html, LitElement } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; -export class MyCustomCardEditor extends LitElement { +export class TeamtrackerCardEditor extends LitElement { static get properties() { return { - hass: {}, - _config: {}, + _config: { type: Object }, + currentPage: { type: String }, + entities: { type: Array }, + hass: { type: Object }, + _entity: { type: String }, }; } - // setConfig works the same way as for the card itself + constructor() { + super(); + this.currentPage = 'card'; + this._entity = ''; + this.entities = []; + this._formValueChanged = this._formValueChanged.bind(this); + } + + setConfig(config) { + if (!config) { + throw new Error("Invalid configuration"); + } this._config = config; + this._entity = config.entity || ''; + } + + + get config() { + return this._config; + } + + updated(changedProperties) { + if (changedProperties.has('hass')) { + this.fetchEntities(); + } + if (changedProperties.has('_config') && this._config && this._config.entity) { + this._entity = this._config.entity; + } + } + + fetchEntities() { + if (this.hass) { + this.entities = Object.keys(this.hass.states).filter((e) => + e.startsWith('sensor.') && + this.hass.states[e].attributes.hasOwnProperty('sport') + ) + .sort((a, b) => a.localeCompare(b)); + } + } + + configChanged(newConfig) { + const event = new Event("config-changed", { + bubbles: true, + composed: true, + }); + event.detail = { config: newConfig }; + this.dispatchEvent(event); + } + + _EntityChanged(event, key) { + if (!this._config) { + return; + } + + const newConfig = { ...this._config }; + + if (key === 'entity') { + newConfig.entity = event.target.value; + this._entity = event.target.value; + } + + this.configChanged(newConfig); + this.requestUpdate(); } + + _valueChanged(event, key) { + if (!this._config) { + return; + } + + let newConfig = { ...this._config }; + + if (key.includes('.')) { + const parts = key.split('.'); + let currentLevel = newConfig; + + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + + currentLevel[part] = { ...currentLevel[part] }; + + currentLevel = currentLevel[part]; + } + + const finalKey = parts[parts.length - 1]; + if (event.target.checked !== undefined) { + currentLevel[finalKey] = event.target.checked; + } else { + currentLevel[finalKey] = event.target.value; + } + } else { + if (event.target.checked !== undefined) { + newConfig[key] = event.target.checked; + } else { + newConfig[key] = event.target.value; + } + } + + this.configChanged(newConfig); + this.requestUpdate(); + } + + _formValueChanged(event) { + if (event.target.tagName.toLowerCase() === 'ha-form') { + const newConfig = event.detail.value; + this.configChanged(newConfig); + this.requestUpdate(); + } + } + // This function is called when the input element of the editor loses focus entityChanged(ev) { @@ -43,13 +153,153 @@ export class MyCustomCardEditor extends LitElement { return html``; } - // @focusout below will call entityChanged when the input field loses focus (e.g. the user tabs away or clicks outside of it) return html` - Entity: - + +
+

Teamtracker Sensor:

+
+ this._EntityChanged(e, 'entity')} + @closed=${(ev) => ev.stopPropagation()} + > + ${this.entities.map((entity) => { + return html`${entity}`; + })} + +
+
+

Settings:

+ this._valueChanged(e, 'home_side')} + @closed=${(ev) => ev.stopPropagation()} + > + Team on Left + Home on Left + Home on Right + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+

Overrides:

+ + +
+ + +
+ + +
+ + +
+
+ + + +
+
`; } } \ No newline at end of file diff --git a/dist/const.js b/dist/const.js index f2af43f..671e48f 100644 --- a/dist/const.js +++ b/dist/const.js @@ -1,4 +1,4 @@ -export let VERSION = "v0.14.1"; +export let VERSION = "v0.14.2"; export let GOLF_HEADSHOT_URL = "https://a.espncdn.com/i/headshots/golf/players/full/"; export let MMA_HEADSHOT_URL = "https://a.espncdn.com/i/headshots/mma/players/full/"; diff --git a/dist/ha-teamtracker-card.js b/dist/ha-teamtracker-card.js index 5560334..8ed746a 100644 --- a/dist/ha-teamtracker-card.js +++ b/dist/ha-teamtracker-card.js @@ -1,10 +1,10 @@ import { VERSION } from "./const.js"; -import { MyCustomCardEditor } from "./card_editor.js"; +import { TeamtrackerCardEditor } from "./card_editor.js"; import { TeamTrackerCard } from "./teamtracker_card.js"; customElements.define("teamtracker-card", TeamTrackerCard); -customElements.define("my-custom-card-editor", MyCustomCardEditor); +customElements.define("teamtracker-card-editor", TeamtrackerCardEditor); console.info("%c TEAMTRACKER-CARD %s IS INSTALLED", "color: blue; font-weight: bold", diff --git a/dist/render_bye.js b/dist/render_bye.js index d74f949..8fb59de 100644 --- a/dist/render_bye.js +++ b/dist/render_bye.js @@ -5,16 +5,6 @@ import { html } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.j export function renderBye(c) { // Render the HTML template using the provided object `c` const htmlTemplate = html` -
diff --git a/dist/render_in.js b/dist/render_in.js index 3c8be7c..f892fb5 100644 --- a/dist/render_in.js +++ b/dist/render_in.js @@ -1,62 +1,10 @@ -import { html } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; +import { html, styleMap } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js"; // Define the rendering function export function renderIn(c) { // Render the HTML template using the provided object `c` const htmlTemplate = html` -
${c.title}
@@ -64,49 +12,47 @@ export function renderIn(c) {
${c.playClock}
-
-
+
+
-
-
+
+
-
+
-
${c.in0}
-
${c.seriesSummary}
-
+
${c.in0}
+
${c.seriesSummary}
+
${c.venue}
@@ -116,17 +62,17 @@ export function renderIn(c) {
${c.location}
${c.in2}
-
-
+
+

${c.lastPlay}

-
+
${c.gameBar}
${c.barLabel[1]}
-
-
+
+
${c.barLabel[2]}
diff --git a/dist/render_not_found.js b/dist/render_not_found.js index 2a67c3b..aa5794f 100644 --- a/dist/render_not_found.js +++ b/dist/render_not_found.js @@ -5,17 +5,6 @@ import { html } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.j export function renderNotFound(c) { // Render the HTML template using the provided object `c` const htmlTemplate = html` -
${c.title}
diff --git a/dist/render_post.js b/dist/render_post.js index 66d1cd3..6cfb032 100644 --- a/dist/render_post.js +++ b/dist/render_post.js @@ -1,33 +1,10 @@ -import { html } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; +import { html, styleMap } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js"; // Define the rendering function export function renderPost(c) { // Render the HTML template using the provided object `c` const htmlTemplate = html` -
${c.finalTerm}
-
${c.seriesSummary}
+
${c.seriesSummary}
diff --git a/dist/render_pre.js b/dist/render_pre.js index 6060316..a62b699 100644 --- a/dist/render_pre.js +++ b/dist/render_pre.js @@ -1,34 +1,10 @@ -import { html } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; +import { html, styleMap } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js"; // Define the rendering function export function renderPre(c) { // Render the HTML template using the provided object `c` const htmlTemplate = html` - -
${c.seriesSummary}
+
${c.seriesSummary}
diff --git a/dist/set_defaults.js b/dist/set_defaults.js index 9c3b09b..16ea950 100644 --- a/dist/set_defaults.js +++ b/dist/set_defaults.js @@ -18,9 +18,11 @@ export function initCardData(c) { c.barLabel = []; c.barLength = []; c.color = []; - c.timeouts = []; c.possessionOp = []; c.winner = []; + c.timeoutsOp = []; + c.timeoutsOp[1] = []; + c.timeoutsOp[2] = []; } // @@ -34,7 +36,6 @@ export function setDefaults(t, lang, stateObj, c, o, sport, team, oppo) { c.outsDisplay = 'none'; c.basesDisplay = 'none'; c.barDisplay = 'inherit'; - c.barWrapDisplay = "flex"; c.timeoutsDisplay = 'inline'; c.rankDisplay = 'inline'; c.seriesSummaryDisplay = 'none'; @@ -137,9 +138,13 @@ export function setDefaults(t, lang, stateObj, c, o, sport, team, oppo) { if (stateObj.attributes.possession == stateObj.attributes.opponent_id) { c.possessionOp[oppo] = 1; } - c.timeouts[team] = stateObj.attributes.team_timeouts; - c.timeouts[oppo] = stateObj.attributes.opponent_timeouts; - + c.timeoutsOp[team][1] = stateObj.attributes.team_timeouts >= 1 ? 1 : 0.2 + c.timeoutsOp[team][2] = stateObj.attributes.team_timeouts >= 2 ? 1 : 0.2; + c.timeoutsOp[team][3] = stateObj.attributes.team_timeouts >= 3 ? 1 : 0.2; + c.timeoutsOp[oppo][1] = stateObj.attributes.opponent_timeouts >= 1 ? 1 : 0.2 + c.timeoutsOp[oppo][2] = stateObj.attributes.opponent_timeouts >= 2 ? 1 : 0.2; + c.timeoutsOp[oppo][3] = stateObj.attributes.opponent_timeouts >= 3 ? 1 : 0.2; + // Set Location / Context data c.startTerm = t.translate(sport + ".startTerm"); @@ -218,11 +223,11 @@ export function setDefaults(t, lang, stateObj, c, o, sport, team, oppo) { export function setCardFormat(o, c) { - c.clrOut = 0; - c.outColor = o.outlineColor; + c.outlineWidth = 0; + c.outlineColor = o.outlineColor; if (o.outline == true) { - c.clrOut = 1; + c.outlineWidth = 1; } } diff --git a/dist/set_sports.js b/dist/set_sports.js index 727ac37..1dc7581 100644 --- a/dist/set_sports.js +++ b/dist/set_sports.js @@ -54,12 +54,10 @@ export function setBaseball(t, stateObj, c, team, oppo) { // setBasketball() // timeoutsDisplay = 'none'; // barDisplay = "none"; -// barWrapDisplay = "none"; // export function setBasketball(t, stateObj, c, team, oppo) { c.timeoutsDisplay = 'none'; c.barDisplay = 'none'; - c.barWrapDisplay = "none"; } @@ -67,7 +65,6 @@ export function setBasketball(t, stateObj, c, team, oppo) { // SetCricket() // timeoutsDisplay = 'none'; // barDisplay = "none"; -// barWrapDisplay = "none"; // in1 = odds; // in2 = quarter; // score = split score into 2 parts @@ -78,7 +75,6 @@ export function setCricket(t, stateObj, c, team, oppo) { c.timeoutsDisplay = 'none'; c.barDisplay = "none"; - c.barWrapDisplay = "none"; c.in1 = stateObj.attributes.odds; c.in2 = stateObj.attributes.quarter; @@ -146,13 +142,11 @@ export function setHockey(t, stateObj, c, team, oppo) { // title = use event_name if title is not set // timeoutsDisplay = 'none'; // barDisplay = "none"; -// barWrapDisplay = "none"; // export function setMMA(t, stateObj, c, team, oppo) { c.title = c.title || stateObj.attributes.event_name; c.timeoutsDisplay = 'none'; c.barDisplay = "none"; - c.barWrapDisplay = "none"; c.logo[team] = MMA_HEADSHOT_URL + stateObj.attributes.team_id + ".png"; c.logo[oppo] = MMA_HEADSHOT_URL + stateObj.attributes.opponent_id + ".png"; @@ -255,8 +249,12 @@ export function setTennis(t, stateObj, c, team, oppo) { else { c.barLabel[oppo] = t.translate("tennis.oppoBarLabel", "%s", String(stateObj.attributes.opponent_score )); } - c.timeouts[team] = stateObj.attributes.team_sets_won; - c.timeouts[oppo] = stateObj.attributes.opponent_sets_won; + c.timeoutsOp[team][1] = stateObj.attributes.team_sets_won >= 1 ? 1 : 0.2 + c.timeoutsOp[team][2] = stateObj.attributes.team_sets_won >= 2 ? 1 : 0.2 + c.timeoutsOp[team][3] = stateObj.attributes.team_sets_won >= 3 ? 1 : 0.2 + c.timeoutsOp[oppo][1] = stateObj.attributes.opponent_sets_won >= 1 ? 1 : 0.2 + c.timeoutsOp[oppo][2] = stateObj.attributes.opponent_sets_won >= 2 ? 1 : 0.2 + c.timeoutsOp[oppo][3] = stateObj.attributes.opponent_sets_won >= 3 ? 1 : 0.2 c.logo[team] = TENNIS_HEADSHOT_URL + stateObj.attributes.team_id + ".png"; c.logo[oppo] = TENNIS_HEADSHOT_URL + stateObj.attributes.opponent_id + ".png"; @@ -280,7 +278,12 @@ export function setVolleyball(t, stateObj, c, team, oppo) { c.barLength[oppo] = stateObj.attributes.opponent_score; c.barLabel[team] = t.translate("volleyball.teamBarLabel", "%s", String(stateObj.attributes.team_score)); c.barLabel[oppo] = t.translate("volleyball.oppoBarLabel", "%s", String(stateObj.attributes.opponent_score)); - c.timeouts[team] = stateObj.attributes.team_sets_won; - c.timeouts[oppo] = stateObj.attributes.opponent_sets_won; + c.timeoutsOp[team][1] = stateObj.attributes.team_sets_won >= 1 ? 1 : 0.2 + c.timeoutsOp[team][2] = stateObj.attributes.team_sets_won >= 2 ? 1 : 0.2 + c.timeoutsOp[team][3] = stateObj.attributes.team_sets_won >= 3 ? 1 : 0.2 + c.timeoutsOp[oppo][1] = stateObj.attributes.opponent_sets_won >= 1 ? 1 : 0.2 + c.timeoutsOp[oppo][2] = stateObj.attributes.opponent_sets_won >= 2 ? 1 : 0.2 + c.timeoutsOp[oppo][3] = stateObj.attributes.opponent_sets_won >= 3 ? 1 : 0.2 + c.timeoutsDisplay = 'inline'; } diff --git a/dist/styles.js b/dist/styles.js new file mode 100644 index 0000000..731361e --- /dev/null +++ b/dist/styles.js @@ -0,0 +1,64 @@ +import { css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js"; + +export const cardStyles = css` +.card { position: relative; overflow: hidden; padding: 16px 16px 20px; font-weight: 400; border-radius: var(--ha-card-border-radius, 10px); } +.title { text-align: center; font-size: 1.2em; font-weight: 500; } +.team-bg { opacity: 0.08; position: absolute; top: -20%; left: -20%; width: 58%; z-index: 0; } +.opponent-bg { opacity: 0.08; position: absolute; top: -20%; right: -20%; width: 58%; z-index: 0; } +.card-content { display: flex; justify-content: space-evenly; align-items: center; text-align: center; position: relative; z-index: 1; } +.team { text-align: center; width: 35%; } +.team img { max-width: 90px; } +.logo { max-height: 6.5em; } +.score { font-size: var(--score_size, 3em); opacity: var(--score_opacity, 1); text-align: center; line-height: 1; } +.line { height: 1px; background-color: var(--primary-text-color); margin:10px 0; } +.left-clickable { text-decoration: none; color: inherit; } +.right-clickable { text-decoration: none; color: inherit; } +.bottom-clickable { text-decoration: none; color: inherit; } +.disabled { pointer-events: none; cursor: default; } + +.possession { opacity: var(--possession-opacity, 1); font-size: 2.5em; text-align: center; font-weight:900; } +.divider { font-size: 2.5em; text-align: center; margin: 0 4px; } +.name { font-size: 1.4em; margin-bottom: 4px; } +.rank { display: var(--rank-display, inline); font-size:0.8em; } +.record { font-size:1.0em; height 1.0em; } +.timeouts-wrapper { margin: 0.4em auto; width: 70%; display: var(--timeouts-display, inline); } +.timeout { height: 0.6em; border-radius: 0.3em; background-color: var(--timeout-color, #000000); border: var(--timeout-border, 1px) solid var(--timeout-border-color, #ffffff); width: 20%; display: inline-block; margin: 0.4em auto; position: relative; opacity: var(--timeout-opacity, 0.2); } +.bases { display: var(--bases-display, inherit); font-size: 2.5em; text-align: center; font-weight:900; } +.on-base { opacity: var(--on-base-opacity, 1); display: inline-block; } +.pitcher { opacity: 0.0; display: inline-block; } +.in-row1 { font-size: 1em; height: 1em; margin: 6px 0 2px; } +.in-row2 { ; font-size: 1em; height: 1em; margin: 6px 0 2px; } +.in-row1, .in-row2 { display: flex; justify-content: space-between; align-items: center; margin: 2px 0; } +.last-play { font-size: 1.2em; width: 100%; white-space: nowrap; overflow: hidden; box-sizing: border-box; } +.last-play p { animation : slide var(--last-play-speed, 18s) linear infinite; display: inline-block; padding-left: 100%; margin: 2px 0 12px; } +@keyframes slide { 0% { transform: translate(0, 0); } 100% { transform: translate(-100%, 0); } } +.down-distance { text-align: right; } +.play-clock { font-size: 1.4em; height: 1.4em; text-align: center; } +.outs { display: var(--outs-display, inherit); text-align: center; } + +.bar-wrapper { display: var(--bar-display, inherit) } +.bar-text { text-align: center; } +.bar-flex { width: 100%; display: flex; justify-content: center; margin-top: 4px; } +.bar-right { width: var(--bar-length, 0); background-color: var(--bar-color, red); height: 0.8em; border-radius: 0 0.4em 0.4em 0; border: var(--bar-border, 1px) solid var(--bar-border-color, lightgrey); border-left: 0; transition: all 1s ease-out; } +.bar-left { width: var(--bar-length, 0); background-color: var(--bar-color, blue); height: 0.8em; border-radius: 0.4em 0 0 0.4em; border: var(--bar-border, 1px) solid var(--bar-border-color, lightgrey); border-right: 0; transition: all 1s ease-out; } +.bar { display: flex; align-items: center; } +.bar1-label { flex: 0 0 10px; padding: 0 10px 0 0; margin-top: 4px; } +.bar2-label { flex: 0 0 10px; padding: 0 0 0 10px; text-align: right; margin-top: 4px; } +.in-series-info { display: var(--series-summary-display, none); font-size: 1.2em; text-align: center; margin: 4px; } + +.gameday { font-size: 1.4em; height: 1.4em; } +.gamedate { font-size: 1.1em; height: 1.1em; } +.gametime { font-size: 1.1em; height: 1.1em; } +.pre-row1 { font-weight: 500; font-size: 1.2em; height: 1.2em; margin: 6px 0 2px; } +.pre-row1, .pre-row2, .pre-row3 { display: flex; justify-content: space-between; align-items: center; margin: 2px 0; } +.pre-series-info { display: var(--series-summary-display, none); font-size: 1.2em; text-align: center; margin: 4px; } + +.post-row1 { font-size: 1.2em; text-align: center; } +.post-series-info { display: var(--series-summary-display, none); font-size: 1.2em; text-align: center; margin: 4px; } + +.notFound1 { font-size: 1.4em; line-height: 1.2em; text-align: center; width: 100%; margin-bottom: 4px; } +.notFound2 { font-size: 1.4em; line-height: 1.2em; text-align: center; width: 100%; margin-bottom: 4px; } + +.bye { font-size: 1.8em; text-align: center; width: 50%; } + +`; \ No newline at end of file diff --git a/dist/teamtracker_card.js b/dist/teamtracker_card.js index 3f0bca4..80928f6 100644 --- a/dist/teamtracker_card.js +++ b/dist/teamtracker_card.js @@ -1,4 +1,4 @@ -import { LitElement } from "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js"; +import { LitElement, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js"; import { Translator } from "./localize/translator.js"; import { VERSION } from "./const.js"; import { renderBye } from './render_bye.js'; @@ -9,6 +9,7 @@ import { renderPost } from './render_post.js'; import { renderPre } from './render_pre.js'; import { initCardData, setCardFormat, setDefaults, setStartInfo } from './set_defaults.js'; import { setSportData } from './set_sports.js'; +import { cardStyles } from './styles.js'; export class TeamTrackerCard extends LitElement { @@ -20,6 +21,12 @@ export class TeamTrackerCard extends LitElement { }; } + static get styles() { + return css` + ${cardStyles} + `; + } + setConfig(config) { this._config = config; this._actionConfig = { @@ -205,10 +212,10 @@ export class TeamTrackerCard extends LitElement { // Trigger the UI Card Editor from Card Picker // Uncomment to enable visual editor // - // static getConfigElement() { - // // Create and return an editor element - // return document.createElement("my-custom-card-editor"); - // } + static getConfigElement() { + // Create and return an editor element + return document.createElement("teamtracker-card-editor"); + } // // static getStubConfig() { // // Return a minimal configuration that will result in a working card configuration