1786380840
This commit is contained in:
@@ -297,7 +297,7 @@ impl HaClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
upcoming.sort_by_key(|event| event.sort_epoch);
|
upcoming.sort_by_key(|event| event.sort_epoch);
|
||||||
upcoming.truncate(7);
|
upcoming.truncate(6);
|
||||||
Ok(upcoming)
|
Ok(upcoming)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,44 +9,8 @@ import {
|
|||||||
} from "material/material.slint";
|
} from "material/material.slint";
|
||||||
import { Icons } from "material/ui/icons/icons.slint";
|
import { Icons } from "material/ui/icons/icons.slint";
|
||||||
|
|
||||||
export struct CalendarEventData {
|
|
||||||
id: string,
|
|
||||||
date: string,
|
|
||||||
time: string,
|
|
||||||
summary: string,
|
|
||||||
calendar: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export component Calendar inherits Rectangle {
|
export component Calendar inherits Rectangle {
|
||||||
in property <[CalendarEventData]> calendar-events;
|
|
||||||
|
|
||||||
VerticalLayout {
|
Rectangle { }
|
||||||
for event[index] in calendar-events: ListTile {
|
|
||||||
avatar_icon: Icons.calendar_today;
|
|
||||||
avatar_foreground: Palette.foreground;
|
|
||||||
text: event.date;
|
|
||||||
supporting-text: event.summary;
|
|
||||||
height: 72px;
|
|
||||||
VerticalLayout {
|
|
||||||
alignment: center;
|
|
||||||
horizontal_stretch: 1;
|
|
||||||
MaterialText {
|
|
||||||
text: event.time;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
color: MaterialPalette.on_surface;
|
|
||||||
overflow: elide;
|
|
||||||
style: MaterialTypography.title_medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialText {
|
|
||||||
text: "";
|
|
||||||
horizontal-alignment: right;
|
|
||||||
overflow: elide;
|
|
||||||
style: MaterialTypography.body_medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
|
|
||||||
import { Icons } from "material/ui/icons/icons.slint";
|
|
||||||
import {
|
|
||||||
ListTile,
|
|
||||||
Vertical,
|
|
||||||
ElevatedButton,
|
|
||||||
SegmentedButton,
|
|
||||||
MaterialPalette,
|
|
||||||
FilledButton,
|
|
||||||
MaterialText,
|
|
||||||
MaterialTypography,
|
|
||||||
MaterialStyleMetrics,
|
|
||||||
} from "material/material.slint";
|
|
||||||
|
|
||||||
export struct WeatherData {
|
|
||||||
icon-index: int,
|
|
||||||
label: string,
|
|
||||||
temperature: float,
|
|
||||||
temperature_unit: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export struct ThermostatData {
|
|
||||||
id: string,
|
|
||||||
name: string,
|
|
||||||
current: float,
|
|
||||||
temperature: float,
|
|
||||||
unit: string,
|
|
||||||
target: int,
|
|
||||||
remainder: int,
|
|
||||||
state: string,
|
|
||||||
available: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
export component Climate inherits Rectangle {
|
|
||||||
in property <WeatherData> weather;
|
|
||||||
in property <[ThermostatData]> thermostats;
|
|
||||||
in property <bool> is-night;
|
|
||||||
|
|
||||||
in property <string> next-event-date;
|
|
||||||
in property <string> next-event-time;
|
|
||||||
in property <string> next-event-summary;
|
|
||||||
|
|
||||||
property <int> icon-index: weather.icon-index == 1 && is-night ? 2 : weather.icon-index;
|
|
||||||
|
|
||||||
callback set_temperature(string, float);
|
|
||||||
callback toggle_thermostat(string);
|
|
||||||
|
|
||||||
property <[image]> icons: [
|
|
||||||
@image-url("icons/sun.svg"),
|
|
||||||
@image-url("icons/cloud-sun.svg"),
|
|
||||||
@image-url("icons/cloud-moon.svg"),
|
|
||||||
@image-url("icons/cloud.svg"),
|
|
||||||
@image-url("icons/cloud-rain.svg"),
|
|
||||||
@image-url("icons/cloud-rain-wind.svg"),
|
|
||||||
@image-url("icons/cloud-lightning.svg"),
|
|
||||||
@image-url("icons/snowflake.svg"),
|
|
||||||
@image-url("icons/cloud-fog.svg"),
|
|
||||||
@image-url("icons/moon.svg"),
|
|
||||||
];
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: MaterialStyleMetrics.spacing_16;
|
|
||||||
HorizontalLayout {
|
|
||||||
Vertical {
|
|
||||||
ListTile {
|
|
||||||
avatar_icon: Icons.event_upcoming;
|
|
||||||
avatar_foreground: Palette.foreground;
|
|
||||||
text: next-event-date;
|
|
||||||
supporting-text: next-event-summary;
|
|
||||||
height: 72px;
|
|
||||||
enabled: false;
|
|
||||||
VerticalLayout {
|
|
||||||
alignment: center;
|
|
||||||
horizontal_stretch: 1;
|
|
||||||
MaterialText {
|
|
||||||
text: next-event-time;
|
|
||||||
color: MaterialPalette.on_surface;
|
|
||||||
overflow: elide;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
style: MaterialTypography.title_medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialText {
|
|
||||||
text: "";
|
|
||||||
overflow: elide;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
style: MaterialTypography.body_medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vertical {
|
|
||||||
Text {
|
|
||||||
text: "\{weather.temperature}\{weather.temperature-unit}";
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: 700;
|
|
||||||
vertical-alignment: center;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: weather.label;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 500;
|
|
||||||
vertical-alignment: center;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
colorize: Palette.foreground;
|
|
||||||
width: 120px;
|
|
||||||
height: 105px;
|
|
||||||
source: icons[icon-index];
|
|
||||||
image-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for thermostat[index] in thermostats: ListTile {
|
|
||||||
avatar_icon: Icons.thermostat;
|
|
||||||
avatar_foreground: Palette.foreground;
|
|
||||||
text: thermostat.name;
|
|
||||||
supporting-text: thermostat.available ? thermostat.state : "UNAVAILABLE";
|
|
||||||
height: 72px;
|
|
||||||
enabled: false;
|
|
||||||
|
|
||||||
ElevatedButton {
|
|
||||||
icon: Icons.remove;
|
|
||||||
clicked => { root.set_temperature(thermostat.id, thermostat.temperature - 0.5) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80px;
|
|
||||||
VerticalLayout {
|
|
||||||
HorizontalLayout {
|
|
||||||
Text {
|
|
||||||
text: thermostat.target;
|
|
||||||
font-size: 40px;
|
|
||||||
vertical-alignment: center;
|
|
||||||
horizontal-alignment: right;
|
|
||||||
font-weight: 700;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding-top: 2px;
|
|
||||||
Text {
|
|
||||||
text: thermostat.unit;
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
vertical-alignment: top;
|
|
||||||
font-weight: 500;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: ".\{thermostat.remainder}";
|
|
||||||
// color: MaterialPalette.primary;
|
|
||||||
font-size: 14px;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
vertical-alignment: bottom;
|
|
||||||
font-weight: 500;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "\{thermostat.current} \{thermostat.unit}";
|
|
||||||
color: MaterialPalette.secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
vertical-alignment: top;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedButton {
|
|
||||||
icon: Icons.add;
|
|
||||||
clicked => { root.set_temperature(thermostat.id, thermostat.temperature + 0.5) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if thermostat.state == "OFF": ElevatedButton {
|
|
||||||
icon: Icons.mode_heat_off;
|
|
||||||
clicked => { root.toggle_thermostat(thermostat.id) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if thermostat.state == "HEAT": FilledButton {
|
|
||||||
icon: Icons.mode_heat;
|
|
||||||
clicked => { root.toggle_thermostat(thermostat.id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,14 @@ import {
|
|||||||
StandardTableView,
|
StandardTableView,
|
||||||
} from "std-widgets.slint";
|
} from "std-widgets.slint";
|
||||||
|
|
||||||
import { Climate, WeatherData, ThermostatData } from "./climate.slint";
|
import {
|
||||||
import { CalendarEventData } from "./calendar.slint";
|
CalendarEventData,
|
||||||
|
MainPage,
|
||||||
|
WeatherData,
|
||||||
|
ThermostatData,
|
||||||
|
} from "./main_page.slint";
|
||||||
|
|
||||||
|
// import { } from "./calendar.slint";
|
||||||
|
|
||||||
import { Calendar } from "calendar.slint";
|
import { Calendar } from "calendar.slint";
|
||||||
import { Energy } from "energy.slint";
|
import { Energy } from "energy.slint";
|
||||||
@@ -181,10 +187,8 @@ export component AppWindow inherits Window {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
width: (root.width / 4) * 3;
|
width: (root.width / 4) * 3;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
if tab-bar.current-index == 0: Climate {
|
MainPage {
|
||||||
next-event-date: next-event-date;
|
calendar-events: calendar-events;
|
||||||
next-event-time: next-event-time;
|
|
||||||
next-event-summary: next-event-summary;
|
|
||||||
|
|
||||||
thermostats: thermostats;
|
thermostats: thermostats;
|
||||||
is-night: is-night;
|
is-night: is-night;
|
||||||
@@ -193,24 +197,16 @@ export component AppWindow inherits Window {
|
|||||||
toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) }
|
toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if tab-bar.current-index == 1: Calendar {
|
// Rectangle {
|
||||||
calendar-events: calendar-events;
|
// tab-bar := TabBar {
|
||||||
}
|
// width: parent.width;
|
||||||
|
// items: [
|
||||||
if tab-bar.current-index == 2: Energy {
|
// { icon: Icons.wb_sunny, text: "Climate" },
|
||||||
render-plot(iteration) => render-plot(iteration);
|
// { icon: Icons.calendar_today, text: "Calendar" },
|
||||||
}
|
// { icon: Icons.energy_savings_leaf, text: "Energy" }
|
||||||
|
// ];
|
||||||
Rectangle {
|
// }
|
||||||
tab-bar := TabBar {
|
// }
|
||||||
width: parent.width;
|
|
||||||
items: [
|
|
||||||
{ icon: Icons.wb_sunny, text: "Climate" },
|
|
||||||
{ icon: Icons.calendar_today, text: "Calendar" },
|
|
||||||
{ icon: Icons.energy_savings_leaf, text: "Energy" }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
241
dashboard/ui/main_page.slint
Normal file
241
dashboard/ui/main_page.slint
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
|
||||||
|
import { Icons } from "material/ui/icons/icons.slint";
|
||||||
|
import {
|
||||||
|
ListTile,
|
||||||
|
Vertical,
|
||||||
|
ElevatedButton,
|
||||||
|
SegmentedButton,
|
||||||
|
MaterialPalette,
|
||||||
|
FilledButton,
|
||||||
|
MaterialText,
|
||||||
|
MaterialTypography,
|
||||||
|
MaterialStyleMetrics,
|
||||||
|
} from "material/material.slint";
|
||||||
|
|
||||||
|
export struct WeatherData {
|
||||||
|
icon-index: int,
|
||||||
|
label: string,
|
||||||
|
temperature: float,
|
||||||
|
temperature_unit: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct ThermostatData {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
current: float,
|
||||||
|
temperature: float,
|
||||||
|
unit: string,
|
||||||
|
target: int,
|
||||||
|
remainder: int,
|
||||||
|
state: string,
|
||||||
|
available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct CalendarEventData {
|
||||||
|
id: string,
|
||||||
|
date: string,
|
||||||
|
time: string,
|
||||||
|
summary: string,
|
||||||
|
calendar: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export component MainPage inherits Rectangle {
|
||||||
|
in property <WeatherData> weather;
|
||||||
|
in property <[ThermostatData]> thermostats;
|
||||||
|
in property <bool> is-night;
|
||||||
|
|
||||||
|
in property <[CalendarEventData]> calendar-events;
|
||||||
|
|
||||||
|
// in property <string> next-event-date;
|
||||||
|
// in property <string> next-event-time;
|
||||||
|
// in property <string> next-event-summary;
|
||||||
|
|
||||||
|
property <int> icon-index: weather.icon-index == 1 && is-night ? 2 : weather.icon-index;
|
||||||
|
|
||||||
|
callback set_temperature(string, float);
|
||||||
|
callback toggle_thermostat(string);
|
||||||
|
|
||||||
|
property <[image]> icons: [
|
||||||
|
@image-url("icons/sun.svg"),
|
||||||
|
@image-url("icons/cloud-sun.svg"),
|
||||||
|
@image-url("icons/cloud-moon.svg"),
|
||||||
|
@image-url("icons/cloud.svg"),
|
||||||
|
@image-url("icons/cloud-rain.svg"),
|
||||||
|
@image-url("icons/cloud-rain-wind.svg"),
|
||||||
|
@image-url("icons/cloud-lightning.svg"),
|
||||||
|
@image-url("icons/snowflake.svg"),
|
||||||
|
@image-url("icons/cloud-fog.svg"),
|
||||||
|
@image-url("icons/moon.svg"),
|
||||||
|
];
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: MaterialStyleMetrics.spacing_16;
|
||||||
|
HorizontalLayout {
|
||||||
|
// Vertical {
|
||||||
|
// ListTile {
|
||||||
|
// avatar_icon: Icons.event_upcoming;
|
||||||
|
// avatar_foreground: Palette.foreground;
|
||||||
|
// text: next-event-date;
|
||||||
|
// supporting-text: next-event-summary;
|
||||||
|
// height: 72px;
|
||||||
|
// enabled: false;
|
||||||
|
// VerticalLayout {
|
||||||
|
// alignment: center;
|
||||||
|
// horizontal_stretch: 1;
|
||||||
|
// MaterialText {
|
||||||
|
// text: next-event-time;
|
||||||
|
// color: MaterialPalette.on_surface;
|
||||||
|
// overflow: elide;
|
||||||
|
// horizontal-alignment: right;
|
||||||
|
// style: MaterialTypography.title_medium;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// MaterialText {
|
||||||
|
// text: "";
|
||||||
|
// overflow: elide;
|
||||||
|
// horizontal-alignment: right;
|
||||||
|
// style: MaterialTypography.body_medium;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Vertical {
|
||||||
|
for thermostat[index] in thermostats: ListTile {
|
||||||
|
avatar_icon: Icons.thermostat;
|
||||||
|
avatar_foreground: Palette.foreground;
|
||||||
|
// text: thermostat.name;
|
||||||
|
// supporting-text: thermostat.available ? thermostat.state : "UNAVAILABLE";
|
||||||
|
height: 72px;
|
||||||
|
enabled: false;
|
||||||
|
|
||||||
|
ElevatedButton {
|
||||||
|
icon: Icons.remove;
|
||||||
|
enabled: thermostat.available;
|
||||||
|
clicked => { root.set_temperature(thermostat.id, thermostat.temperature - 0.5) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 80px;
|
||||||
|
VerticalLayout {
|
||||||
|
HorizontalLayout {
|
||||||
|
Text {
|
||||||
|
text: thermostat.target;
|
||||||
|
font-size: 40px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
font-weight: 700;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
padding-top: 2px;
|
||||||
|
Text {
|
||||||
|
text: thermostat.unit;
|
||||||
|
font-size: 14px;
|
||||||
|
horizontal-alignment: left;
|
||||||
|
vertical-alignment: top;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: ".\{thermostat.remainder}";
|
||||||
|
// color: MaterialPalette.primary;
|
||||||
|
font-size: 14px;
|
||||||
|
horizontal-alignment: left;
|
||||||
|
vertical-alignment: bottom;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "\{thermostat.current} \{thermostat.unit}";
|
||||||
|
color: MaterialPalette.secondary;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-alignment: top;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElevatedButton {
|
||||||
|
icon: Icons.add;
|
||||||
|
enabled: thermostat.available;
|
||||||
|
clicked => { root.set_temperature(thermostat.id, thermostat.temperature + 0.5) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if thermostat.state == "OFF": ElevatedButton {
|
||||||
|
icon: Icons.mode_heat_off;
|
||||||
|
enabled: thermostat.available;
|
||||||
|
clicked => { root.toggle_thermostat(thermostat.id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if thermostat.state == "HEAT": FilledButton {
|
||||||
|
icon: Icons.mode_heat;
|
||||||
|
clicked => { root.toggle_thermostat(thermostat.id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vertical {
|
||||||
|
Text {
|
||||||
|
text: "\{weather.temperature}\{weather.temperature-unit}";
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: weather.label;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
vertical-alignment: center;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
colorize: Palette.foreground;
|
||||||
|
width: 120px;
|
||||||
|
height: 105px;
|
||||||
|
source: icons[icon-index];
|
||||||
|
image-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
for event[index] in calendar-events: ListTile {
|
||||||
|
avatar_icon: Icons.calendar_today;
|
||||||
|
avatar_foreground: Palette.foreground;
|
||||||
|
text: event.date;
|
||||||
|
supporting-text: event.summary;
|
||||||
|
height: 72px;
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
horizontal_stretch: 1;
|
||||||
|
MaterialText {
|
||||||
|
text: event.time;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
color: MaterialPalette.on_surface;
|
||||||
|
overflow: elide;
|
||||||
|
style: MaterialTypography.title_medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialText {
|
||||||
|
text: "";
|
||||||
|
horizontal-alignment: right;
|
||||||
|
overflow: elide;
|
||||||
|
style: MaterialTypography.body_medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user