feat: add weather source entity and weather-reactive CSS source type
Some checks failed
Lint & Test / test (push) Failing after 34s
Some checks failed
Lint & Test / test (push) Failing after 34s
New standalone WeatherSource entity with pluggable provider architecture (Open-Meteo v1, free, no API key). Full CRUD, test endpoint, browser geolocation, IconSelect provider picker, CardSection with test/clone/edit. WeatherColorStripStream maps WMO weather codes to ambient color palettes with temperature hue shifting and thunderstorm flash effects. Ref-counted WeatherManager polls API and caches data per source. CSS editor integration: weather type with EntitySelect source picker, speed and temperature influence sliders. Backup/restore support. i18n for en/ru/zh.
This commit is contained in:
@@ -210,6 +210,7 @@
|
||||
{% include 'modals/value-source-editor.html' %}
|
||||
{% include 'modals/test-value-source.html' %}
|
||||
{% include 'modals/sync-clock-editor.html' %}
|
||||
{% include 'modals/weather-source-editor.html' %}
|
||||
{% include 'modals/settings.html' %}
|
||||
|
||||
{% include 'partials/tutorial-overlay.html' %}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<option value="notification" data-i18n="color_strip.type.notification">Notification</option>
|
||||
<option value="daylight" data-i18n="color_strip.type.daylight">Daylight Cycle</option>
|
||||
<option value="candlelight" data-i18n="color_strip.type.candlelight">Candlelight</option>
|
||||
<option value="weather" data-i18n="color_strip.type.weather">Weather</option>
|
||||
<option value="processed" data-i18n="color_strip.type.processed">Processed</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -566,6 +567,38 @@
|
||||
</div>
|
||||
|
||||
<!-- Processed type fields -->
|
||||
<!-- Weather-specific fields -->
|
||||
<div id="css-editor-weather-section" style="display:none">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="css-editor-weather-source" data-i18n="color_strip.weather.source">Weather Source:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="color_strip.weather.source.hint">The weather data source to use for ambient colors</small>
|
||||
<select id="css-editor-weather-source">
|
||||
<option value="">—</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="css-editor-weather-speed"><span data-i18n="color_strip.weather.speed">Animation Speed:</span> <span id="css-editor-weather-speed-val">1.0</span></label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="color_strip.weather.speed.hint">Speed of the ambient color drift animation</small>
|
||||
<input type="range" id="css-editor-weather-speed" min="0.1" max="5" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('css-editor-weather-speed-val').textContent = parseFloat(this.value).toFixed(1)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="css-editor-weather-temp-influence"><span data-i18n="color_strip.weather.temperature_influence">Temperature Influence:</span> <span id="css-editor-weather-temp-val">0.5</span></label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="color_strip.weather.temperature_influence.hint">How much the current temperature shifts the color palette warm/cool. 0 = pure condition colors, 1 = strong shift.</small>
|
||||
<input type="range" id="css-editor-weather-temp-influence" min="0" max="1" step="0.05" value="0.5"
|
||||
oninput="document.getElementById('css-editor-weather-temp-val').textContent = parseFloat(this.value).toFixed(2)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="css-editor-processed-section" style="display:none">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!-- Weather Source Editor Modal -->
|
||||
<div id="weather-source-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="weather-source-modal-title">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="weather-source-modal-title" data-i18n="weather_source.add">Add Weather Source</h2>
|
||||
<button class="modal-close-btn" onclick="closeWeatherSourceModal()" data-i18n-aria-label="aria.close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="weather-source-form" onsubmit="return false;">
|
||||
<input type="hidden" id="weather-source-id">
|
||||
|
||||
<div id="weather-source-error" class="error-message" style="display: none;"></div>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="weather-source-name" data-i18n="weather_source.name">Name:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="weather_source.name.hint">A descriptive name for this weather source</small>
|
||||
<input type="text" id="weather-source-name" data-i18n-placeholder="weather_source.name.placeholder" placeholder="My Weather" required>
|
||||
<div id="weather-source-tags-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- Provider -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="weather-source-provider" data-i18n="weather_source.provider">Provider:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="weather_source.provider.hint">Weather data provider. Open-Meteo is free and requires no API key.</small>
|
||||
<select id="weather-source-provider">
|
||||
<option value="open_meteo">Open-Meteo</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="weather_source.location">Location:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="weather_source.location.hint">Your geographic coordinates. Use the auto-detect button or enter manually.</small>
|
||||
<div class="weather-location-row">
|
||||
<div class="weather-location-field">
|
||||
<label for="weather-source-latitude" data-i18n="weather_source.latitude">Lat:</label>
|
||||
<input type="number" id="weather-source-latitude" min="-90" max="90" step="0.01" value="50.0">
|
||||
</div>
|
||||
<div class="weather-location-field">
|
||||
<label for="weather-source-longitude" data-i18n="weather_source.longitude">Lon:</label>
|
||||
<input type="number" id="weather-source-longitude" min="-180" max="180" step="0.01" value="0.0">
|
||||
</div>
|
||||
<button type="button" class="btn btn-icon btn-secondary" id="weather-source-geolocate-btn"
|
||||
onclick="weatherSourceGeolocate()" title="Use my location" data-i18n-title="weather_source.use_my_location">
|
||||
<svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<small id="weather-source-location-name" class="input-hint" style="display:none"></small>
|
||||
</div>
|
||||
|
||||
<!-- Update Interval -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="weather-source-interval"><span data-i18n="weather_source.update_interval">Update Interval:</span> <span id="weather-source-interval-display">10</span> min</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="weather_source.update_interval.hint">How often to fetch weather data. Lower values give more responsive changes.</small>
|
||||
<input type="range" id="weather-source-interval" min="60" max="3600" step="60" value="600"
|
||||
oninput="document.getElementById('weather-source-interval-display').textContent = Math.round(this.value / 60)">
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="weather-source-description" data-i18n="weather_source.description">Description (optional):</label>
|
||||
</div>
|
||||
<input type="text" id="weather-source-description" data-i18n-placeholder="weather_source.description.placeholder" placeholder="">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeWeatherSourceModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
||||
<button class="btn btn-icon btn-secondary" id="weather-source-test-btn" onclick="testWeatherSource()" title="Test" data-i18n-title="weather_source.test">
|
||||
<svg class="icon" viewBox="0 0 24 24"><path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/><path d="M6.453 15h11.094"/><path d="M8.5 2h7"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveWeatherSource()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user