diff --git a/scripts/translations/fetch.py b/scripts/translations/fetch.py index 3afc718b..a2e490dc 100755 --- a/scripts/translations/fetch.py +++ b/scripts/translations/fetch.py @@ -100,6 +100,8 @@ def process_dir(_dir, _output, _keys): ("return_home", ["ui.dialogs.more_info_control.vacuum.return_home"]), ("start_pause", ["ui.dialogs.more_info_control.vacuum.start_pause"]), ("battery", ["ui.dialogs.entity_registry.editor.device_classes.binary_sensor.battery"]), + ("weather_forecast", ["ui.panel.lovelace.editor.card.weather-forecast.name"]), + ("count", ["ui.panel.config.automation.editor.actions.type.repeat.count"]), ("set_white", ["ui.dialogs.more_info_control.light.set_white"]), ("vacuum_commands", ["ui.dialogs.more_info_control.vacuum.commands"]), ("target", ["ui.card.water_heater.target"]), diff --git a/src/lib/Modal/SidebarItemConfig.svelte b/src/lib/Modal/SidebarItemConfig.svelte index ce8742f0..df9981d9 100644 --- a/src/lib/Modal/SidebarItemConfig.svelte +++ b/src/lib/Modal/SidebarItemConfig.svelte @@ -22,6 +22,7 @@ import Divider from '$lib/Sidebar/Divider.svelte'; import Navigate from '$lib/Sidebar/Navigate.svelte'; import Weather from '$lib/Sidebar/Weather.svelte'; + import WeatherForecast from '$lib/Sidebar/WeatherForecast.svelte'; import Iframe from '$lib/Sidebar/Iframe.svelte'; import Image from '$lib/Sidebar/Image.svelte'; import Camera from '$lib/Sidebar/Camera.svelte'; @@ -180,6 +181,14 @@ entity_id: 'weather.openweathermap' } }, + { + id: 'weatherforecast', + type: $lang('weather_forecast'), + component: WeatherForecast, + props: { + entity_id: 'weather.forecast_home' + } + }, { id: 'navigate', type: $lang('navigate'), @@ -230,6 +239,9 @@ case 'weather': openModal(() => import('$lib/Modal/WeatherConfig.svelte'), { sel }); break; + case 'weatherforecast': + openModal(() => import('$lib/Modal/WeatherForecastConfig.svelte'), { sel }); + break; case 'camera': openModal(() => import('$lib/Modal/CameraConfig.svelte'), { sel, diff --git a/src/lib/Modal/WeatherForecastConfig.svelte b/src/lib/Modal/WeatherForecastConfig.svelte new file mode 100644 index 00000000..b67a7546 --- /dev/null +++ b/src/lib/Modal/WeatherForecastConfig.svelte @@ -0,0 +1,118 @@ + + +{#if isOpen} + +

{$lang('weather_forecast')}

+ +

{$lang('preview')}

+ +
+ +
+ +

{$lang('entity')}

+ + {#if weatherStates} + set('icon_pack', event)} + /> + {/if} + +

{$lang('count')}

+ + {#if weatherStates} + + {/if} + + +
+{/if} diff --git a/src/lib/Sidebar/Index.svelte b/src/lib/Sidebar/Index.svelte index 3ef0a965..1ed91e7c 100644 --- a/src/lib/Sidebar/Index.svelte +++ b/src/lib/Sidebar/Index.svelte @@ -28,6 +28,7 @@ let Time: typeof import('$lib/Sidebar/Time.svelte'); let Timer: typeof import('$lib/Sidebar/Timer.svelte'); let Weather: typeof import('$lib/Sidebar/Weather.svelte'); + let WeatherForecast: typeof import('$lib/Sidebar/WeatherForecast.svelte'); const importsMap = { bar: () => import('$lib/Sidebar/Bar.svelte').then((module) => (Bar = module)), @@ -45,7 +46,9 @@ template: () => import('$lib/Sidebar/Template.svelte').then((module) => (Template = module)), time: () => import('$lib/Sidebar/Time.svelte').then((module) => (Time = module)), timer: () => import('$lib/Sidebar/Timer.svelte').then((module) => (Timer = module)), - weather: () => import('$lib/Sidebar/Weather.svelte').then((module) => (Weather = module)) + weather: () => import('$lib/Sidebar/Weather.svelte').then((module) => (Weather = module)), + weatherforecast: () => + import('$lib/Sidebar/WeatherForecast.svelte').then((module) => (WeatherForecast = module)) }; $: if ($dashboard?.sidebar) importComponents(); @@ -114,6 +117,8 @@ openModal(() => import('$lib/Modal/TimerConfig.svelte'), { sel }); } else if (sel?.type === 'weather') { openModal(() => import('$lib/Modal/WeatherConfig.svelte'), { sel }); + } else if (sel?.type === 'weatherforecast') { + openModal(() => import('$lib/Modal/WeatherForecastConfig.svelte'), { sel }); } else { openModal(() => import('$lib/Modal/SidebarItemConfig.svelte'), { sel }); @@ -325,6 +330,17 @@ show_apparent={item?.show_apparent} /> + + + {:else if WeatherForecast && item?.type === 'weatherforecast'} + {/if} {/each} diff --git a/src/lib/Sidebar/WeatherForecast.svelte b/src/lib/Sidebar/WeatherForecast.svelte new file mode 100644 index 00000000..87d767c4 --- /dev/null +++ b/src/lib/Sidebar/WeatherForecast.svelte @@ -0,0 +1,155 @@ + + +{#if entity_state} +
+ {#each forecast as forecast, i} +
+
+ {#if forecast_diff < 24} + {new Intl.DateTimeFormat($selectedLanguage, { hour: 'numeric' }).format( + new Date(forecast.date) + )} + {:else} + {new Intl.DateTimeFormat($selectedLanguage, { weekday: 'short' }).format( + new Date(forecast.date) + )} + {/if} +
+ + {#if forecast.icon.local} + + {entity_state} + + {:else} + + {/if} + +
+ {Math.round(forecast.temperature)}{attributes?.temperature_unit || '°'} +
+
+ {/each} +
+{:else} +
+ {$lang('weather_forecast')} +
+{/if} + + diff --git a/src/lib/Types.ts b/src/lib/Types.ts index 1d3ad6b5..6af8cc61 100644 --- a/src/lib/Types.ts +++ b/src/lib/Types.ts @@ -83,6 +83,7 @@ export type SidebarItem = BarItem & TemplateItem & TimeItem & WeatherItem & + WeatherForecastItem & DividerItem; export interface BarItem { @@ -198,3 +199,12 @@ export interface WeatherItem { extra_sensor_icon?: string; show_apparent?: boolean; } + +export interface WeatherForecastItem { + type?: string; + id?: number; + entity_id?: string; + state?: string; + icon_pack?: string; + number_of_items?: number; +} diff --git a/src/lib/Weather.ts b/src/lib/Weather.ts new file mode 100644 index 00000000..283785de --- /dev/null +++ b/src/lib/Weather.ts @@ -0,0 +1,253 @@ +import { readable } from 'svelte/store'; + +// A set of icons, e.g. meteocons, weather icons +export interface WeatherIconSet { + type: string; + conditions: WeatherIconConditions; +} + +// Conditions as defined in +export interface WeatherIconConditions { + 'clear-night': WeatherIconMapping; + cloudy: WeatherIconMapping; + fog: WeatherIconMapping; + hail: WeatherIconMapping; + lightning: WeatherIconMapping; + 'lightning-rainy': WeatherIconMapping; + partlycloudy: WeatherIconMapping; + pouring: WeatherIconMapping; + rainy: WeatherIconMapping; + snowy: WeatherIconMapping; + 'snowy-rainy': WeatherIconMapping; + sunny: WeatherIconMapping; + windy: WeatherIconMapping; + 'windy-variant': WeatherIconMapping; + exceptional: WeatherIconMapping; +} + +// Icon variants for day & night. Icons which are either-or should be the same icon (clear-night, sunny, potentially others depending on the library) +export interface WeatherIconMapping { + local?: boolean; + icon_variant_day: string; + icon_variant_night: string; +} + +export const iconMapMaterialSymbolsLight: WeatherIconSet = { + type: 'materialsymbolslight', + conditions: { + 'clear-night': { + icon_variant_day: 'material-symbols-light:bedtime-outline-rounded', + icon_variant_night: 'material-symbols-light:bedtime-outline-rounded' + }, + cloudy: { + icon_variant_day: 'material-symbols-light:cloud-outline', + icon_variant_night: 'material-symbols-light:cloud-outline' + }, + exceptional: { + icon_variant_day: 'material-symbols-light:warning-outline-rounded', + icon_variant_night: 'material-symbols-light:warning-outline-rounded' + }, + fog: { + icon_variant_day: 'material-symbols-light:foggy-outline', + icon_variant_night: 'material-symbols-light:foggy-outline' + }, + hail: { + icon_variant_day: 'material-symbols-light:weather-hail-outline-rounded', + icon_variant_night: 'material-symbols-light:weather-hail-outline-rounded' + }, + lightning: { + icon_variant_day: 'material-symbols-light:thunderstorm-outline-rounded', + icon_variant_night: 'material-symbols-light:thunderstorm-outline-rounded' + }, + 'lightning-rainy': { + icon_variant_day: 'material-symbols-light:thunderstorm-outline-rounded', + icon_variant_night: 'material-symbols-light:thunderstorm-outline-rounded' + }, + partlycloudy: { + icon_variant_day: 'material-symbols-light:partly-cloudy-day-outline', + icon_variant_night: 'material-symbols-light:nights-stay-outline-rounded' + }, + pouring: { + icon_variant_day: 'material-symbols-light:rainy-outline', + icon_variant_night: 'material-symbols-light:rainy-outline' + }, + rainy: { + icon_variant_day: 'material-symbols-light:rainy-outline', + icon_variant_night: 'material-symbols-light:rainy-outline' + }, + snowy: { + icon_variant_day: 'material-symbols-light:cloudy-snowing-outline', + icon_variant_night: 'material-symbols-light:cloudy-snowing-outline' + }, + 'snowy-rainy': { + icon_variant_day: 'material-symbols-light:weather-mix-outline-rounded', + icon_variant_night: 'material-symbols-light:weather-mix-outline-rounded' + }, + sunny: { + icon_variant_day: 'material-symbols-light:sunny-outline-rounded', + icon_variant_night: 'material-symbols-light:sunny-outline-rounded' + }, + windy: { + icon_variant_day: 'material-symbols-light:mist', + icon_variant_night: 'material-symbols-light:mist' + }, + 'windy-variant': { + icon_variant_day: 'material-symbols-light:mistd', + icon_variant_night: 'material-symbols-light:mist' + } + } +}; + +export const iconMapMeteocons: WeatherIconSet = { + type: 'meteocons', + conditions: { + 'clear-night': { + local: true, + icon_variant_day: '/weather/meteocons/clear-night-day', + icon_variant_night: '/weather/meteocons/clear-night-night' + }, + cloudy: { + local: true, + icon_variant_day: '/weather/meteocons/cloudy-day', + icon_variant_night: '/weather/meteocons/cloudy-night' + }, + exceptional: { + local: true, + icon_variant_day: '/weather/meteocons/exceptional-day', + icon_variant_night: '/weather/meteocons/exceptional-night' + }, + fog: { + local: true, + icon_variant_day: '/weather/meteocons/fog-day', + icon_variant_night: '/weather/meteocons/fog-night' + }, + hail: { + local: true, + icon_variant_day: '/weather/meteocons/hail-day', + icon_variant_night: '/weather/meteocons/hail-night' + }, + lightning: { + local: true, + icon_variant_day: '/weather/meteocons/lightning-day', + icon_variant_night: '/weather/meteocons/lightning-night' + }, + 'lightning-rainy': { + local: true, + icon_variant_day: '/weather/meteocons/lightning-rainy-day', + icon_variant_night: '/weather/meteocons/lightning-rainy-night' + }, + partlycloudy: { + local: true, + icon_variant_day: '/weather/meteocons/partlycloudy-day', + icon_variant_night: '/weather/meteocons/partlycloudy-night' + }, + pouring: { + local: true, + icon_variant_day: '/weather/meteocons/pouring-day', + icon_variant_night: '/weather/meteocons/pouring-night' + }, + rainy: { + local: true, + icon_variant_day: '/weather/meteocons/rainy-day', + icon_variant_night: '/weather/meteocons/rainy-night' + }, + snowy: { + local: true, + icon_variant_day: '/weather/meteocons/snowy-day', + icon_variant_night: '/weather/meteocons/snowy-night' + }, + 'snowy-rainy': { + local: true, + icon_variant_day: '/weather/meteocons/snowy-rainy-day', + icon_variant_night: '/weather/meteocons/snowy-rainy-night' + }, + sunny: { + local: true, + icon_variant_day: '/weather/meteocons/sunny-day', + icon_variant_night: '/weather/meteocons/sunny-night' + }, + windy: { + local: true, + icon_variant_day: '/weather/meteocons/windy-day', + icon_variant_night: '/weather/meteocons/windy-night' + }, + 'windy-variant': { + local: true, + icon_variant_day: '/weather/meteocons/windy-variant-day', + icon_variant_night: '/weather/meteocons/windy-variant-night' + } + } +}; + +export const iconMapWeatherIcons: WeatherIconSet = { + type: 'weathericons', + conditions: { + 'clear-night': { + icon_variant_day: 'wi:night-clear', + icon_variant_night: 'wi:night-clear' + }, + cloudy: { + icon_variant_day: 'wi:day-cloudy', + icon_variant_night: 'wi:night-alt-cloudy' + }, + exceptional: { + icon_variant_day: 'wi:na', + icon_variant_night: 'wi:na' + }, + fog: { + icon_variant_day: 'wi:day-fog', + icon_variant_night: 'wi:night-fog' + }, + hail: { + icon_variant_day: 'wi:day-hail', + icon_variant_night: 'wi:night-alt-hail' + }, + lightning: { + icon_variant_day: 'wi:day-lightning', + icon_variant_night: 'wi:night-alt-lightning' + }, + 'lightning-rainy': { + icon_variant_day: 'wi:day-thunderstorm', + icon_variant_night: 'wi:night-alt-thunderstorm' + }, + partlycloudy: { + icon_variant_day: 'wi:day-cloudy', + icon_variant_night: 'wi:night-alt-cloudy' + }, + pouring: { + icon_variant_day: 'wi:day-rain', + icon_variant_night: 'wi:night-alt-rain' + }, + rainy: { + icon_variant_day: 'wi:day-rain', + icon_variant_night: 'wi:night-alt-rain' + }, + snowy: { + icon_variant_day: 'wi:day-snow', + icon_variant_night: 'wi:night-alt-snow' + }, + 'snowy-rainy': { + icon_variant_day: 'wi:day-sleet', + icon_variant_night: 'wi:night-alt-sleet' + }, + sunny: { + icon_variant_day: 'wi:day-sunny', + icon_variant_night: 'wi:day-sunny' + }, + windy: { + icon_variant_day: 'wi:strong-wind', + icon_variant_night: 'wi:strong-wind' + }, + 'windy-variant': { + icon_variant_day: 'wi:strong-wind', + icon_variant_night: 'wi:strong-wind' + } + } +}; + +// Weather icon mapping +export const iconMap = readable>({ + materialsymbolslight: iconMapMaterialSymbolsLight, + meteocons: iconMapMeteocons, + weathericons: iconMapWeatherIcons +});