Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,33 @@ const LitElement = customElements.get("hui-masonry-view")
: Object.getPrototypeOf(customElements.get("hui-view"));

const html = LitElement.prototype.html;
const css = LitElement.prototype.css;

class TaskMateHeatmapCardEditor extends LitElement {
static get properties() {
return { hass: { type: Object }, config: { type: Object } };
}

static get styles() {
return css`
:host { display: block; }
ha-form { display: block; margin-bottom: 16px; }
.colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
.colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
.colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
.colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
.colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
.colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
.colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
.preset-swatch:hover { transform: scale(1.15); }
.preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
.colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-inline-start: auto; }
.colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
`;
}

_t(key, params) {
const fn = window.__taskmate_localize;
return fn ? fn(this.hass, key, params) : key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,33 @@ const LitElement = customElements.get("hui-masonry-view")
: Object.getPrototypeOf(customElements.get("hui-view"));

const html = LitElement.prototype.html;
const css = LitElement.prototype.css;

class TaskMatePersonalBestsCardEditor extends LitElement {
static get properties() {
return { hass: { type: Object }, config: { type: Object } };
}

static get styles() {
return css`
:host { display: block; }
ha-form { display: block; margin-bottom: 16px; }
.colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
.colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
.colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
.colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
.colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
.colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
.colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
.preset-swatch:hover { transform: scale(1.15); }
.preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
.colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-inline-start: auto; }
.colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
`;
}

_t(key, params) {
const fn = window.__taskmate_localize;
return fn ? fn(this.hass, key, params) : key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,23 @@ class TaskMateRaffleCardEditor extends LitElement {
}

static get styles() {
return css`:host { display: block; } ha-form { display: block; margin-bottom: 16px; }`;
return css`
:host { display: block; }
ha-form { display: block; margin-bottom: 16px; }
.colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
.colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
.colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
.colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
.colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
.colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
.colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
.preset-swatch:hover { transform: scale(1.15); }
.preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
.colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-inline-start: auto; }
.colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
`;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
/**
* TaskMate routine card editor (visual card editor).
*
* Moved out of taskmate-routine-card.js with the other editors. The card has
* never had a getConfigElement(), so Home Assistant does not open this editor
* yet (the Routine card is configured in YAML); wiring it up needs its English
* labels translated and its mwc-list-item options replaced first.
* Loaded on demand by taskmate-routine-card.js getConfigElement(), so a dashboard
* never downloads or parses it until someone opens the card editor.
*/

const LitElement = customElements.get("hui-masonry-view")
Expand All @@ -16,85 +14,164 @@ const css = LitElement.prototype.css;

// Shared with taskmate-routine-card.js; tests/frontend/taskmate-lazy-editors.test.cjs keeps the copies equal.
const TIME_CATEGORIES = ["all", "morning", "afternoon", "evening", "night", "anytime"];
const DEFAULT_ACCENT = "#7c3aed";

class TaskMateRoutineCardEditor extends LitElement {
static get properties() {
return { hass: { type: Object }, _config: { type: Object } };
return { hass: { type: Object }, config: { type: Object } };
}

setConfig(config) { this._config = { ...config }; }

_t(key, params) {
const fn = window.__taskmate_localize;
return fn ? fn(this.hass, key, params) : key;
}

static get styles() {
return css`
:host { display: block; }
ha-form { display: block; margin-bottom: 16px; }
.colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
.colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
.colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
.colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
.colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
.colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
.colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
.preset-swatch:hover { transform: scale(1.15); }
.preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
.colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-inline-start: auto; }
.colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
`;
}

setConfig(config) { this.config = config; }

_buildSchema() {
const entity = this.config && this.config.entity ? this.hass && this.hass.states && this.hass.states[this.config.entity] : null;
const attrs = window.__taskmate_attrs && this.config && this.config.entity
? window.__taskmate_attrs(this.hass, this.config.entity)
: (entity && entity.attributes) || {};
const children = Array.isArray(attrs.children) ? attrs.children : [];
return [
{ name: "entity", selector: { entity: { domain: "sensor" } } },
{
name: "child_id",
selector: {
select: { options: children.map((c) => ({ value: c.id, label: c.name })), mode: "dropdown" },
},
},
{
name: "time_category",
selector: {
select: {
options: TIME_CATEGORIES.map((value) => ({ value, label: this._t(`routine.editor.time_${value}`) })),
mode: "dropdown",
},
},
},
{ name: "title", selector: { text: {} } },
{
name: "card_design",
selector: {
select: {
options: window.__taskmate_design
? window.__taskmate_design.editorOptions(this._t.bind(this))
: [{ value: "global", label: "Use global default" }],
mode: "dropdown",
},
},
},
];
}

_computeLabel = (entry) => {
const labels = {
entity: this._t("common.editor.overview_entity"),
child_id: this._t("routine.editor.child"),
time_category: this._t("routine.editor.time_category"),
title: this._t("common.editor.card_title"),
card_design: this._t("common.design.field_label"),
};
return labels[entry.name] ?? entry.name;
};

_computeHelper = (entry) => {
const helpers = {
entity: this._t("common.editor.overview_entity_helper"),
child_id: this._t("routine.editor.child_helper"),
time_category: this._t("routine.editor.time_category_helper"),
title: this._t("routine.editor.title_helper"),
};
return helpers[entry.name] ?? "";
};

render() {
if (!this._config) return html``;
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this._config.entity)) || {};
const children = attrs.children || [];
if (!this.hass || !this.config) return html``;
const data = {
entity: this.config.entity || "",
child_id: this.config.child_id || "",
time_category: this.config.time_category || "all",
title: this.config.title || "",
card_design: this.config.card_design || "global",
};
return html`
<div class="form">
<ha-textfield
label="Entity"
.value=${this._config.entity || "sensor.taskmate_overview"}
@input=${e => this._set("entity", e.target.value)}
></ha-textfield>

<ha-select
label="Child"
.value=${this._config.child_id || ""}
@selected=${e => this._set("child_id", e.target.value)}
@closed=${e => e.stopPropagation()}
>
${children.map(c => html`<mwc-list-item .value=${c.id}>${c.name}</mwc-list-item>`)}
</ha-select>

<ha-select
label="Time of day"
.value=${this._config.time_category || "all"}
@selected=${e => this._set("time_category", e.target.value)}
@closed=${e => e.stopPropagation()}
>
${TIME_CATEGORIES.map(t => html`<mwc-list-item .value=${t}>${t}</mwc-list-item>`)}
</ha-select>

<ha-textfield
label="Title (optional)"
.value=${this._config.title || ""}
@input=${e => this._set("title", e.target.value)}
></ha-textfield>

<ha-select
.label=${this._t("common.design.field_label")}
.value=${this._config.card_design || "global"}
@selected=${e => this._set("card_design", e.target.value)}
@closed=${e => e.stopPropagation()}
>
${(window.__taskmate_design
? window.__taskmate_design.editorOptions(this._t.bind(this))
: [{ value: "global", label: "Use global default" }]
).map(o => html`<mwc-list-item .value=${o.value}>${o.label}</mwc-list-item>`)}
</ha-select>
</div>
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${this._buildSchema()}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
@value-changed=${this._formChanged}
></ha-form>
${this._renderColourPicker("header_color", DEFAULT_ACCENT)}
`;
}

_set(key, value) {
const newConfig = { ...this._config, [key]: value };
// "global" is the absence of a per-card override, not a value to store.
if (key === "card_design" && value === "global") delete newConfig[key];
else if (value === null || value === "" || value === undefined) delete newConfig[key];
this._config = newConfig;
this.dispatchEvent(new CustomEvent("config-changed", {
detail: { config: newConfig }, bubbles: true, composed: true,
}));
_renderColourPicker(key, defaultValue) {
const d = window.__taskmate_design;
if (!d || !d.colourPicker) return html``;
return d.colourPicker({
defaultValue,
current: this.config[key] || defaultValue,
label: this._t("common.editor.header_colour"),
helper: this._t("common.editor.header_colour_helper"),
resetLabel: this._t("common.reset"),
onInput: (v) => this._updateConfig(key, v),
onPreset: (v) => this._updateConfig(key, v),
onReset: () => this._updateConfig(key, defaultValue),
});
}

static get styles() {
return css`
.form { display: flex; flex-direction: column; gap: 14px; padding: 8px 0; }
`;
_formChanged(e) {
const newValues = e.detail.value || {};
const newConfig = { ...this.config };
for (const [key, value] of Object.entries(newValues)) {
// "all" and "global" are the card's defaults, not values to store.
if (
value === "" || value === null || value === undefined
|| (key === "card_design" && value === "global")
|| (key === "time_category" && value === "all")
) {
delete newConfig[key];
} else {
newConfig[key] = value;
}
}
this._fire(newConfig);
}

_updateConfig(key, value) {
const newConfig = { ...this.config, [key]: value };
if (value === null || value === "" || value === undefined) delete newConfig[key];
this._fire(newConfig);
}

_fire(config) {
this.dispatchEvent(new CustomEvent("config-changed", {
detail: { config }, bubbles: true, composed: true,
}));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ class TaskMateWishlistCardEditor extends LitElement {
return css`
:host { display: block; }
ha-form { display: block; margin-bottom: 16px; }
.colour-field { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; border: 1px solid var(--outline-color, var(--divider-color, #e0e0e0)); border-radius: 4px; background: var(--mdc-text-field-fill-color, var(--card-background-color)); }
.colour-field-label { font-size: 0.82rem; color: var(--primary-color); font-weight: 500; }
.colour-field-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.colour-swatch-wrapper { position: relative; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); flex-shrink: 0; }
.colour-swatch-wrapper input[type="color"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; border: 0; padding: 0; }
.colour-swatch-preview { position: absolute; inset: 0; pointer-events: none; }
.colour-hex { font-family: var(--code-font-family, monospace); font-size: 0.85rem; color: var(--secondary-text-color); min-width: 70px; }
.colour-presets { display: flex; gap: 6px; flex-wrap: wrap; }
.preset-swatch { width: 22px; height: 22px; border-radius: 50%; cursor: pointer; border: 2px solid var(--divider-color, #e0e0e0); transition: transform 0.1s; padding: 0; }
.preset-swatch:hover { transform: scale(1.15); }
.preset-swatch.active { border-color: var(--primary-text-color); box-shadow: 0 0 0 2px var(--primary-color); }
.colour-reset { font-size: 0.78rem; color: var(--secondary-text-color); background: none; border: 1px solid var(--divider-color, #e0e0e0); border-radius: 4px; padding: 4px 10px; cursor: pointer; margin-inline-start: auto; }
.colour-helper { color: var(--secondary-text-color); font-size: 0.82rem; line-height: 1.3; }
`;
}

Expand Down
11 changes: 11 additions & 0 deletions custom_components/taskmate/www/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -3154,6 +3154,17 @@
"routine.start_again": "Start forfra",
"routine.empty_title": "Intet at lave",
"routine.empty_body": "Denne rutine er allerede klaret.",
"routine.editor.child": "Barn",
"routine.editor.child_helper": "Hvis rutine kortet guider igennem, én opgave ad gangen.",
"routine.editor.time_category": "Tidspunkt på dagen",
"routine.editor.time_category_helper": "Hvilke pligter rutinen består af: kun pligter sat til dette tidspunkt på dagen. Hele dagen tager alle pligter, der skal gøres i dag.",
"routine.editor.time_all": "Hele dagen",
"routine.editor.time_morning": "Morgen",
"routine.editor.time_afternoon": "Eftermiddag",
"routine.editor.time_evening": "Aften",
"routine.editor.time_night": "Nat",
"routine.editor.time_anytime": "Pligter når som helst",
"routine.editor.title_helper": "Lad feltet stå tomt for \"<navn>s rutine\"",
"kiosk.who": "Hvem er der?",
"kiosk.tap_face": "Tryk på dit ansigt",
"kiosk.progress": "{done} af {total} klaret",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/taskmate/www/locales/da/editor.json

Large diffs are not rendered by default.

Loading
Loading