Web card configuration visual modernization

This commit is contained in:
Nicole 2025-11-16 00:35:35 +01:00
parent ff244641e8
commit 07ec766383

View file

@ -527,7 +527,7 @@ class MapBadgeCardEditor extends HTMLElement {
const zonesHtml = Object.entries(this._config.zones) const zonesHtml = Object.entries(this._config.zones)
.map(([state, config], idx) => ` .map(([state, config], idx) => `
<div class="config-item"> <div class="config-item">
<div class="input-wrapper" style="flex: 1;"> <div class="input-wrapper">
<label>State Name</label> <label>State Name</label>
<input <input
type="text" type="text"
@ -538,9 +538,9 @@ class MapBadgeCardEditor extends HTMLElement {
data-zone-field="state" data-zone-field="state"
placeholder="home"> placeholder="home">
</div> </div>
<div class="input-wrapper" style="width: 80px;"> <div class="input-wrapper" style="flex: 0 0 100px;">
<label>Color</label> <label>Color</label>
<input type="color" value="${config.color}" data-zone-idx="${idx}" data-zone-field="color" style="width: 100%; height: 40px; border-radius: 4px; border: 1px solid var(--divider-color); cursor: pointer;"> <input type="color" value="${config.color}" data-zone-idx="${idx}" data-zone-field="color" class="entity-input" style="height: 40px; padding: 4px;">
</div> </div>
<ha-icon-button <ha-icon-button
data-zone-delete="${idx}"> data-zone-delete="${idx}">
@ -636,7 +636,7 @@ class MapBadgeCardEditor extends HTMLElement {
const entitiesHtml = this._config.entities const entitiesHtml = this._config.entities
.map((entity, idx) => ` .map((entity, idx) => `
<div class="config-item"> <div class="config-item">
<div class="input-wrapper" style="flex: 1;"> <div class="input-wrapper">
<label>Person Entity</label> <label>Person Entity</label>
<input <input
type="text" type="text"
@ -648,7 +648,7 @@ class MapBadgeCardEditor extends HTMLElement {
placeholder="person.example" placeholder="person.example"
list="person-entities-list"> list="person-entities-list">
</div> </div>
<div class="input-wrapper" style="flex: 1;"> <div class="input-wrapper">
<label>Activity Sensor</label> <label>Activity Sensor</label>
<input <input
type="text" type="text"
@ -670,58 +670,75 @@ class MapBadgeCardEditor extends HTMLElement {
this.innerHTML = ` this.innerHTML = `
<style> <style>
.config-container { .config-container {
padding: 20px; padding: 24px;
max-width: 800px; max-width: 900px;
margin: 0 auto;
} }
.config-header { .config-header {
font-size: 20px; font-size: 24px;
font-weight: 600; font-weight: 700;
margin-bottom: 24px; margin-bottom: 32px;
color: var(--primary-text-color); color: var(--primary-text-color);
padding-bottom: 12px; padding-bottom: 16px;
border-bottom: 2px solid var(--divider-color); border-bottom: 3px solid var(--primary-color);
} }
.config-row { .config-row {
margin: 20px 0; margin: 24px 0;
transition: all 0.3s ease;
} }
.config-row ha-textfield, .config-row ha-textfield,
.config-row ha-select { .config-row ha-select {
width: 100%; width: 100%;
--mdc-theme-primary: var(--primary-color);
} }
.config-note { .config-note {
font-size: 12px; font-size: 13px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
margin-top: 6px; margin-top: 8px;
font-style: italic; padding: 8px 12px;
background: var(--secondary-background-color);
border-radius: 6px;
border-left: 3px solid var(--primary-color);
} }
.config-section { .config-section {
margin: 32px 0; margin: 32px 0;
padding: 20px; padding: 24px;
background: var(--card-background-color); background: var(--card-background-color);
border-radius: 12px; border-radius: 16px;
border: 1px solid var(--divider-color); border: 2px solid var(--divider-color);
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}
.config-section:hover {
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
} }
.config-section-header { .config-section-header {
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 700;
margin-bottom: 20px; margin-bottom: 24px;
color: var(--primary-text-color); color: var(--primary-text-color);
display: flex; padding-bottom: 12px;
align-items: center; border-bottom: 2px solid var(--divider-color);
gap: 10px;
}
.config-section-header ha-icon {
flex-shrink: 0;
} }
.config-item { .config-item {
display: flex; display: flex;
gap: 12px; gap: 12px;
margin-bottom: 16px; margin-bottom: 16px;
align-items: flex-end; align-items: flex-end;
padding: 12px;
background: var(--secondary-background-color);
border-radius: 12px;
transition: background 0.2s ease;
}
.config-item:hover {
background: var(--divider-color);
} }
.add-button { .add-button {
margin-top: 12px; margin-top: 16px;
width: 100%;
--mdc-theme-primary: var(--primary-color);
border-radius: 8px;
font-weight: 600;
} }
ha-icon-button[data-entity-delete], ha-icon-button[data-entity-delete],
ha-icon-button[data-zone-delete], ha-icon-button[data-zone-delete],
@ -733,195 +750,248 @@ class MapBadgeCardEditor extends HTMLElement {
.input-wrapper { .input-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 0;
min-width: 0;
max-width: 100%;
} }
.input-wrapper label { .input-wrapper label {
font-size: 12px; font-size: 13px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
margin-bottom: 4px; margin-bottom: 6px;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
} }
.entity-input { .entity-input {
width: 100%; width: 100%;
padding: 8px 12px; max-width: 100%;
border: 1px solid var(--divider-color); box-sizing: border-box;
border-radius: 4px; padding: 10px 14px;
border: 2px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color); background: var(--card-background-color);
color: var(--primary-text-color); color: var(--primary-text-color);
font-family: inherit; font-family: inherit;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.3s ease, box-shadow 0.3s ease;
} }
.entity-input:focus { .entity-input:focus {
outline: none; outline: none;
border-color: var(--primary-color); border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(var(--rgb-primary-color), 0.1);
}
.entity-input:hover {
border-color: var(--primary-color);
} }
.entity-input::placeholder { .entity-input::placeholder {
color: var(--secondary-text-color); color: var(--secondary-text-color);
opacity: 0.5; opacity: 0.6;
} }
.radius-slider { .radius-slider {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none;
appearance: none; appearance: none;
height: 6px; width: 100%;
border-radius: 3px; height: 8px;
border-radius: 4px;
background: var(--divider-color); background: var(--divider-color);
outline: none; outline: none;
transition: background 0.2s; cursor: pointer;
pointer-events: auto;
} }
.radius-slider:hover { .radius-slider::-webkit-slider-runnable-track {
background: var(--secondary-text-color); width: 100%;
height: 8px;
border-radius: 4px;
background: var(--divider-color);
}
.radius-slider::-moz-range-track {
width: 100%;
height: 8px;
border-radius: 4px;
background: var(--divider-color);
} }
.radius-slider::-webkit-slider-thumb { .radius-slider::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
width: 18px; width: 20px;
height: 18px; height: 20px;
border-radius: 50%; border-radius: 50%;
background: var(--primary-color); background: var(--primary-color);
cursor: pointer; cursor: grab;
transition: transform 0.2s; box-shadow: 0 2px 6px rgba(0,0,0,0.2);
margin-top: -6px;
} }
.radius-slider::-webkit-slider-thumb:hover { .radius-slider::-webkit-slider-thumb:active {
transform: scale(1.2); cursor: grabbing;
} }
.radius-slider::-moz-range-thumb { .radius-slider::-moz-range-thumb {
width: 18px; width: 20px;
height: 18px; height: 20px;
border-radius: 50%; border-radius: 50%;
background: var(--primary-color); background: var(--primary-color);
cursor: pointer; cursor: grab;
border: none; border: none;
transition: transform 0.2s; box-shadow: 0 2px 6px rgba(0,0,0,0.2);
} }
.radius-slider::-moz-range-thumb:hover { .radius-slider::-moz-range-thumb:active {
transform: scale(1.2); cursor: grabbing;
}
/* Modern toggle button for API key */
#toggle-api-key-visibility {
transition: color 0.2s ease;
}
#toggle-api-key-visibility:hover {
color: var(--primary-color);
}
/* Grid layout improvements */
@media (min-width: 600px) {
.config-row {
display: grid;
gap: 16px;
}
} }
</style> </style>
<div class="config-container"> <div class="config-container">
<div class="config-header">Map Badge Card Configuration</div> <div class="config-header">
Map Badge Card Configuration
<div class="config-row"> </div>
<ha-select
id="map_provider" <div class="config-section">
label="Map Provider" <div class="config-section-header">
value="${this._config.map_provider || 'osm'}"> Map Provider Settings
<mwc-list-item value="osm">OpenStreetMap (No API Key Required)</mwc-list-item> </div>
<mwc-list-item value="google">Google Maps</mwc-list-item> <div class="config-row">
</ha-select> <ha-select
<div class="config-note">OpenStreetMap is free and requires no authentication</div> id="map_provider"
</div> label="Map Provider"
value="${this._config.map_provider || 'osm'}">
<div class="config-row" id="google-api-key-row" style="display: ${this._config.map_provider === 'google' ? 'block' : 'none'}"> <mwc-list-item value="osm">OpenStreetMap (No API Key Required)</mwc-list-item>
<ha-textfield <mwc-list-item value="google">Google Maps</mwc-list-item>
id="google_api_key" </ha-select>
label="Google API Key" <div class="config-note">OpenStreetMap is free and requires no authentication</div>
value="${this._config.google_api_key || ''}" </div>
placeholder="AIzaSy...">
</ha-textfield> <div class="config-row" id="google-api-key-row" style="display: ${this._config.map_provider === 'google' ? 'block' : 'none'}">
<div class="config-note">Required only for Google Maps - Get your API key from Google Cloud Console</div> <div style="position: relative;">
</div> <ha-textfield
id="google_api_key"
<div class="config-row" id="map-type-row" style="display: ${this._config.map_provider === 'google' ? 'block' : 'none'}"> label="Google API Key"
<ha-select value="${this._config.google_api_key || ''}"
id="map_type" placeholder="AIzaSy..."
label="Map Type" type="password">
value="${this._config.map_type || 'hybrid'}"> </ha-textfield>
<mwc-list-item value="hybrid">Hybrid</mwc-list-item> <ha-icon-button
<mwc-list-item value="satellite">Satellite</mwc-list-item> id="toggle-api-key-visibility"
<mwc-list-item value="roadmap">Roadmap</mwc-list-item> style="position: absolute; right: 0; top: 8px;">
<mwc-list-item value="terrain">Terrain</mwc-list-item> <ha-icon icon="mdi:eye"></ha-icon>
</ha-select> </ha-icon-button>
<div class="config-note">Map type (Google Maps only)</div> </div>
</div> <div class="config-note">Required only for Google Maps - Get your API key from Google Cloud Console</div>
</div>
<div class="config-row">
<ha-textfield <div class="config-row" id="map-type-row" style="display: ${this._config.map_provider === 'google' ? 'block' : 'none'}">
id="default_zoom" <ha-select
label="Default Zoom" id="map_type"
value="${this._config.default_zoom || 13}" label="Map Type"
type="number" value="${this._config.map_type || 'hybrid'}">
min="1" <mwc-list-item value="hybrid">Hybrid</mwc-list-item>
max="21"> <mwc-list-item value="satellite">Satellite</mwc-list-item>
</ha-textfield> <mwc-list-item value="roadmap">Roadmap</mwc-list-item>
<div class="config-note">1 = World view, 21 = Maximum zoom</div> <mwc-list-item value="terrain">Terrain</mwc-list-item>
</div> </ha-select>
<div class="config-note">Map type (Google Maps only)</div>
<div class="config-row"> </div>
<ha-textfield
id="update_interval" <div class="config-row">
label="Update Interval (seconds)" <ha-textfield
value="${this._config.update_interval || 10}" id="default_zoom"
type="number" label="Default Zoom"
min="1"> value="${this._config.default_zoom || 13}"
</ha-textfield> type="number"
<div class="config-note">How often to refresh location data</div> min="1"
</div> max="21">
</ha-textfield>
<div class="config-row"> <div class="config-note">1 = World view, 21 = Maximum zoom</div>
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--primary-text-color);"> </div>
Marker Border Radius
</label> <div class="config-row">
<div style="display: flex; align-items: center; gap: 12px;"> <ha-textfield
<input id="update_interval"
type="range" label="Update Interval (seconds)"
id="marker_border_radius" value="${this._config.update_interval || 10}"
min="0" type="number"
max="50" min="1">
value="${parseInt(this._config.marker_border_radius) || 50}" </ha-textfield>
class="radius-slider" <div class="config-note">How often to refresh location data</div>
style="flex: 1;"> </div>
<span id="marker-radius-value" style="min-width: 40px; text-align: right; font-weight: 600;">${this._config.marker_border_radius || '50%'}</span> </div>
</div>
<div class="config-note">0% for square, 50% for circle</div> <div class="config-section">
</div> <div class="config-section-header">
Appearance Settings
<div class="config-row"> </div>
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--primary-text-color);"> <div class="config-row">
Badge Border Radius <label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--primary-text-color);">
</label> Marker Border Radius
<div style="display: flex; align-items: center; gap: 12px;"> </label>
<input <div style="display: flex; align-items: center; gap: 12px; width: 100%;">
type="range" <input
id="badge_border_radius" type="range"
min="0" id="marker_border_radius"
max="50" min="0"
value="${parseInt(this._config.badge_border_radius) || 50}" max="50"
class="radius-slider" value="${parseInt(this._config.marker_border_radius) || 50}"
style="flex: 1;"> class="radius-slider">
<span id="badge-radius-value" style="min-width: 40px; text-align: right; font-weight: 600;">${this._config.badge_border_radius || '50%'}</span> <span id="marker-radius-value" style="min-width: 50px; text-align: right; font-weight: 600;">${this._config.marker_border_radius || '50%'}</span>
</div> </div>
<div class="config-note">0% for square, 50% for circle</div> <div class="config-note">0% for square, 50% for circle</div>
</div>
<div class="config-row">
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--primary-text-color);">
Badge Border Radius
</label>
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
<input
type="range"
id="badge_border_radius"
min="0"
max="50"
value="${parseInt(this._config.badge_border_radius) || 50}"
class="radius-slider">
<span id="badge-radius-value" style="min-width: 50px; text-align: right; font-weight: 600;">${this._config.badge_border_radius || '50%'}</span>
</div>
<div class="config-note">0% for square, 50% for circle</div>
</div>
</div> </div>
<div class="config-section"> <div class="config-section">
<div class="config-section-header"> <div class="config-section-header">
<ha-icon icon="mdi:account-multiple"></ha-icon>
Entities Entities
</div> </div>
<div id="entities-container">${entitiesHtml}</div> <div id="entities-container">${entitiesHtml}</div>
${personDatalist} ${personDatalist}
${sensorDatalist} ${sensorDatalist}
<ha-button class="add-button" id="add-entity"> <ha-button class="add-button" id="add-entity">
<ha-icon icon="mdi:plus" slot="icon"></ha-icon>
Add Entity Add Entity
</ha-button> </ha-button>
</div> </div>
<div class="config-section"> <div class="config-section">
<div class="config-section-header"> <div class="config-section-header">
<ha-icon icon="mdi:map-marker"></ha-icon>
Zone Configuration Zone Configuration
</div> </div>
<div id="zones-container">${zonesHtml}</div> <div id="zones-container">${zonesHtml}</div>
<ha-button class="add-button" id="add-zone"> <ha-button class="add-button" id="add-zone">
<ha-icon icon="mdi:plus" slot="icon"></ha-icon>
Add Zone Add Zone
</ha-button> </ha-button>
</div> </div>
<div class="config-section"> <div class="config-section">
<div class="config-section-header"> <div class="config-section-header">
<ha-icon icon="mdi:walk"></ha-icon>
Activity Configuration Activity Configuration
</div> </div>
<div id="activities-container">${activitiesHtml}</div> <div id="activities-container">${activitiesHtml}</div>
@ -956,6 +1026,17 @@ class MapBadgeCardEditor extends HTMLElement {
this._fireChanged(); this._fireChanged();
}); });
// Toggle API key visibility
this.querySelector('#toggle-api-key-visibility')?.addEventListener('click', (e) => {
const apiKeyField = this.querySelector('#google_api_key');
const toggleIcon = e.currentTarget.querySelector('ha-icon');
if (apiKeyField) {
const isPassword = apiKeyField.getAttribute('type') === 'password';
apiKeyField.setAttribute('type', isPassword ? 'text' : 'password');
toggleIcon.setAttribute('icon', isPassword ? 'mdi:eye-off' : 'mdi:eye');
}
});
// Basic config - using 'change' event for ha-textfield components // Basic config - using 'change' event for ha-textfield components
this.querySelector('#google_api_key')?.addEventListener('change', (e) => { this.querySelector('#google_api_key')?.addEventListener('change', (e) => {
this._config.google_api_key = e.target.value; this._config.google_api_key = e.target.value;
@ -980,6 +1061,9 @@ class MapBadgeCardEditor extends HTMLElement {
markerSlider?.addEventListener('input', (e) => { markerSlider?.addEventListener('input', (e) => {
const value = e.target.value + '%'; const value = e.target.value + '%';
markerValueDisplay.textContent = value; markerValueDisplay.textContent = value;
});
markerSlider?.addEventListener('change', (e) => {
const value = e.target.value + '%';
this._config.marker_border_radius = value; this._config.marker_border_radius = value;
this._fireChanged(); this._fireChanged();
}); });
@ -990,6 +1074,9 @@ class MapBadgeCardEditor extends HTMLElement {
badgeSlider?.addEventListener('input', (e) => { badgeSlider?.addEventListener('input', (e) => {
const value = e.target.value + '%'; const value = e.target.value + '%';
badgeValueDisplay.textContent = value; badgeValueDisplay.textContent = value;
});
badgeSlider?.addEventListener('change', (e) => {
const value = e.target.value + '%';
this._config.badge_border_radius = value; this._config.badge_border_radius = value;
this._fireChanged(); this._fireChanged();
}); });