Web card configuration visual modernization
This commit is contained in:
parent
ff244641e8
commit
07ec766383
1 changed files with 240 additions and 153 deletions
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue