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