Compare commits

...
Author SHA1 Message Date
renovate[bot] 6b060d3082 Update pnpm to v12 (#54486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-30 21:41:00 +02:00
Bram Kragten 2f8bdf4260 Bump saturation of the default light mode map (#54492)
bump saturation of the default light mode map
2026-09-30 19:28:52 +00:00
Paul Bottein ee1e27772f Show people and zones on the demo map (#54496) 2026-09-30 21:19:22 +02:00
12 changed files with 481 additions and 116 deletions
+2
View File
@@ -29,6 +29,7 @@ import { mockLabelRegistry } from "./stubs/label_registry";
import { mockIcons } from "./stubs/icons";
import { mockHistory } from "./stubs/history";
import { mockLovelace } from "./stubs/lovelace";
import { zoneRegistryEntries } from "./stubs/map";
import { mockMediaPlayer } from "./stubs/media_player";
import { mockPersistentNotification } from "./stubs/persistent_notification";
import { mockRecorder } from "./stubs/recorder";
@@ -181,6 +182,7 @@ export class HaDemo extends HomeAssistantAppEl {
modified_at: 0,
},
...connectivityEntityRegistryEntries,
...zoneRegistryEntries,
]);
hass.addEntities(energyEntities());
-50
View File
@@ -1,53 +1,3 @@
export const mapEntities = () =>
Object.values({
"zone.home": {
entity_id: "zone.home",
state: "zoning",
attributes: {
hidden: true,
latitude: 52.3631339,
longitude: 4.8903147,
radius: 200,
friendly_name: "Home",
icon: "hademo:home",
},
},
"zone.uva": {
entity_id: "zone.buckhead",
state: "zoning",
attributes: {
hidden: true,
radius: 400,
friendly_name: "UvA",
icon: "hademo:school",
latitude: 52.3558182,
longitude: 4.9535376,
},
},
"person.arsaboo": {
entity_id: "person.arsaboo",
state: "not_home",
attributes: {
radius: 50,
friendly_name: "Arsaboo",
latitude: 52.3579946,
longitude: 4.8664597,
entity_picture: "/assets/arsaboo/images/arsaboo.jpg",
},
},
"person.melody": {
entity_id: "person.melody",
state: "not_home",
attributes: {
radius: 50,
friendly_name: "Melody",
latitude: 52.3408927,
longitude: 4.8711073,
entity_picture: "/assets/arsaboo/images/melody.jpg",
},
},
});
export const energyEntities = () =>
Object.values({
"sensor.grid_fossil_fuel_percentage": {
+2 -2
View File
@@ -9,7 +9,7 @@ import {
import "../custom-cards/cast-demo-row";
import "../custom-cards/ha-demo-card";
import "../custom-cards/ha-demo-next-card";
import { mapEntities } from "./entities";
import { mockMap } from "./map";
export const mockLovelace = (
hass: MockHomeAssistant,
@@ -17,7 +17,7 @@ export const mockLovelace = (
) => {
hass.mockWS("lovelace/config", ({ url_path }) => {
if (url_path === "map") {
hass.addEntities(mapEntities());
mockMap(hass);
return {
strategy: {
type: "map",
+243
View File
@@ -0,0 +1,243 @@
import type { EntityRegistryEntry } from "../../../src/data/entity/entity_registry";
import type { EntityHistoryState } from "../../../src/data/history";
import type { Zone } from "../../../src/data/zone";
import { demoConfig } from "../../../src/fake_data/demo_config";
import type { EntityInput } from "../../../src/fake_data/entities/types";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
const HOUR = 3600 * 1000;
type ZoneData = Omit<Zone, "id">;
const HOME: ZoneData = {
name: "Home",
icon: "mdi:home",
latitude: demoConfig.latitude,
longitude: demoConfig.longitude,
radius: 100,
passive: false,
};
export const zones: Zone[] = [
{
id: "gym",
name: "Gym",
icon: "mdi:dumbbell",
latitude: 52.358,
longitude: 4.8686,
radius: 150,
passive: false,
},
{
id: "school",
name: "School",
icon: "mdi:school",
latitude: 52.3558182,
longitude: 4.9535376,
radius: 250,
passive: false,
},
{
id: "supermarket",
name: "Supermarket",
icon: "mdi:cart",
latitude: 52.3793,
longitude: 4.8994,
radius: 100,
passive: false,
},
{
id: "work",
name: "Work",
icon: "mdi:briefcase",
latitude: 52.3909184,
longitude: 4.8530821,
radius: 200,
passive: false,
},
];
interface DemoPerson {
id: string;
name: string;
picture: string;
latitude: number;
longitude: number;
// Hours ago and the state entered then, oldest first. The first entry is
// the state before the 24 hour activity window, the last one the current
// state.
timeline: [number, string][];
}
const PEOPLE: DemoPerson[] = [
{
id: "arsaboo",
name: "Arsaboo",
picture: "/assets/arsaboo/images/arsaboo.jpg",
latitude: 52.3732,
longitude: 4.8904,
timeline: [
[30, "home"],
[10, "not_home"],
[9.5, "Work"],
[5, "not_home"],
[4.6, "Gym"],
[3.4, "not_home"],
[3.1, "Supermarket"],
[2.8, "not_home"],
[2.4, "home"],
],
},
{
id: "melody",
name: "Melody",
picture: "/assets/arsaboo/images/melody.jpg",
latitude: 52.373,
longitude: 4.8901,
timeline: [
[30, "home"],
[9, "not_home"],
[8.6, "School"],
[4, "not_home"],
[3.6, "home"],
],
},
{
id: "oscar",
name: "Oscar",
picture: "/assets/kernehed/oscar.jpg",
latitude: 52.391,
longitude: 4.8532,
timeline: [
[30, "home"],
[9, "not_home"],
[8.7, "Work"],
[4.5, "not_home"],
[4.2, "Supermarket"],
[3.8, "not_home"],
[3.5, "Work"],
],
},
{
id: "bella",
name: "Bella",
picture: "/assets/kernehed/bella.jpg",
latitude: 52.365,
longitude: 4.8795,
timeline: [
[30, "home"],
[8, "not_home"],
[7.6, "Work"],
[1.2, "not_home"],
[0.9, "Gym"],
[0.3, "not_home"],
],
},
];
const CAR: EntityInput = {
entity_id: "device_tracker.car",
state: "not_home",
attributes: {
friendly_name: "Car",
icon: "mdi:car",
source_type: "gps",
latitude: 52.3805,
longitude: 4.8718,
gps_accuracy: 10,
},
};
const lastMove = (person: DemoPerson) =>
person.timeline[person.timeline.length - 1];
const zoneEntity = (entityId: string, zone: ZoneData): EntityInput => {
// A person's state is "home" for the home zone, the zone name otherwise
const personState = entityId === "zone.home" ? "home" : zone.name;
const persons = PEOPLE.filter(
(person) => lastMove(person)[1] === personState
).map((person) => `person.${person.id}`);
return {
entity_id: entityId,
state: String(persons.length),
attributes: {
latitude: zone.latitude,
longitude: zone.longitude,
radius: zone.radius,
passive: zone.passive,
persons,
editable: true,
icon: zone.icon,
friendly_name: zone.name,
},
};
};
export const zoneEntities = (): EntityInput[] => [
zoneEntity("zone.home", HOME),
...zones.map((zone) => zoneEntity(`zone.${zone.id}`, zone)),
];
// UI zones are in the entity registry, so the zone editor does not also list
// them as YAML zones
export const zoneRegistryEntries: EntityRegistryEntry[] = zones.map((zone) => ({
config_entry_id: null,
config_subentry_id: null,
device_id: null,
area_id: null,
disabled_by: null,
entity_id: `zone.${zone.id}`,
id: `zone.${zone.id}`,
name: null,
icon: null,
labels: [],
categories: {},
platform: "zone",
hidden_by: null,
entity_category: null,
has_entity_name: false,
unique_id: zone.id,
options: null,
created_at: 0,
modified_at: 0,
}));
const personEntity = (person: DemoPerson, now: number): EntityInput => {
const [hoursAgo, state] = lastMove(person);
return {
entity_id: `person.${person.id}`,
state,
last_changed: new Date(now - hoursAgo * HOUR).toISOString(),
attributes: {
friendly_name: person.name,
entity_picture: person.picture,
latitude: person.latitude,
longitude: person.longitude,
gps_accuracy: 20,
},
};
};
const personHistory = (person: DemoPerson, now: number): EntityHistoryState[] =>
person.timeline.map(([hoursAgo, state]) => {
const time = (now - hoursAgo * HOUR) / 1000;
return { s: state, a: {}, lu: time, lc: time };
});
export const mockMap = (hass: MockHomeAssistant) => {
const now = Date.now();
hass.addEntities([
...zoneEntities(),
...PEOPLE.map((person) => personEntity(person, now)),
CAR,
]);
hass.mockWS(
"history/history_during_period",
({ entity_ids }: { entity_ids?: string[] }) =>
Object.fromEntries(
PEOPLE.filter(
(person) => !entity_ids || entity_ids.includes(`person.${person.id}`)
).map((person) => [`person.${person.id}`, personHistory(person, now)])
)
);
};
+2 -22
View File
@@ -1,27 +1,7 @@
import type { Zone } from "../../../src/data/zone";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
const zones: Zone[] = [
{
id: "home",
name: "Home",
icon: "mdi:home",
latitude: 52.3731339,
longitude: 4.8903147,
radius: 100,
passive: false,
},
{
id: "work",
name: "Work",
icon: "mdi:briefcase",
latitude: 52.3909184,
longitude: 4.8530821,
radius: 200,
passive: false,
},
];
import { zoneEntities, zones } from "./map";
export const mockZone = (hass: MockHomeAssistant) => {
hass.mockWS("zone/list", () => zones);
hass.addEntities(zoneEntities());
};
+1 -1
View File
@@ -227,7 +227,7 @@
"webpackbar": "7.0.0",
"workbox-build": "7.4.1"
},
"packageManager": "pnpm@11.28.0",
"packageManager": "pnpm@12.7.0",
"volta": {
"node": "24.21.0"
}
+158 -1
View File
@@ -1,3 +1,161 @@
---
lockfileVersion: '9.0'
importers:
.:
configDependencies: {}
packageManagerDependencies:
pnpm:
specifier: 12.7.0
version: 12.7.0
packages:
'@pnpm/[email protected]':
resolution: {integrity: sha512-gJTCsUbazAEbIMF9l2t+z3YWHCI0AiThtBsxU6FrxXK0tZsBRZZWpleLs0uY8Dy6V0RcPEusn3UyuAyFn3dkeA==}
cpu: [arm64]
os: [android]
'@pnpm/[email protected]':
resolution: {integrity: sha512-7187pEwqAwbrmXesKiCXfmQUARVacBooJDTWqL+9olZh7sZwnECq5pB/KzuYZbnmdHZUkBWRyvChzKojmtSE7Q==}
cpu: [x64]
os: [android]
'@pnpm/[email protected]':
resolution: {integrity: sha512-ppzJ2ln60/Oq0BJpy5bHqwZb0q1Trg/+xb7myxjaIgZfaybwnH3OuFX5w8PPQdRrjYwljgygYLC/7LMtZc/iuQ==}
cpu: [arm64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-+qxS/A5O4rFD7T+qqWnWjESbKVAzKsh9LubDHt9xZ34AbwVk3ZFcAW/7vX5R1jqq+uGgziMPtcVJKc5CICVHCQ==}
cpu: [x64]
os: [darwin]
'@pnpm/[email protected]':
resolution: {integrity: sha512-uv0x93hoecec7IlT/FCwQe2siStGspG7G3EZQtIHH2n3ClDzbsQcEiW1QbMoAyZoLVrR7Pm9ovTndUCRcZ5y1g==}
cpu: [x64]
os: [freebsd]
'@pnpm/[email protected]':
resolution: {integrity: sha512-XZlRiVL/l8mOhPyaocElpaHq6rKHDvkP/oyo1hHgUePX+XzBtQ0KLEgkBLZbk1/f5yGWimQTPmBNvzaYzQLdOg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-vwPQU+Bt3qXMhzjcmMa25W3yP8OyF9yFxHoojr10QJm9lRUmz/SwtM2oHbHKZKkyid/ngmMARXsChFyuRQig5w==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-qzE3TEXYLw0gR2ytclbsGeQ7cA981GeTHMAn86cw9+Jn8nQNa8LZ5ZzCxKRXtGvkEN/FDc0oyGdtILT4JHSStg==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-bTw09mf/v+AuftWePL7pO3WZR0p52A9flr8zpGlcvxKUAKRTE2BDOVeZsWk/8f01ZaO2/0ECtEds6CKfec591w==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-frVk6Ilh2sKVs+CINnJCp3+kiGRi/cE3DARmFftljlrGX09livS4UU37uc7EsAxI4Kbt0WZjeTVT2q6XbVJCrA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-AhoNY7xkhUb0GLqjrtxmxKZsoNM1Jea/VH/ypxt9nyBA5gXJGXo6uM+2Pode3vrWf1rSiVGicokkyEZLkDKf+A==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/[email protected]':
resolution: {integrity: sha512-gGW7NJFmr33IJ6KZu+1w90KBtFxMVf/+AUG8aKJpy4v6dRnUd5v84PcH2ejUuxp/fm3zM0IY6h9LwcYPu9dxdg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@pnpm/[email protected]':
resolution: {integrity: sha512-cI+aZwzOCQc7hQwCA1BydiM3FpxVHdfdMzI3jOG7MQ0d8NHvk5MrdKTTKiKJH2L5VdwWoWHFXbnvhCEyN/fVsg==}
cpu: [arm64]
os: [win32]
'@pnpm/[email protected]':
resolution: {integrity: sha512-+eZ6gFsDbdyuw7GO7amdRh+GpjrQOIe9qjjOmeznWeEsfEeD8GNSrRXtbWe/zpPcxheexAnO7UCeavkhHLVFWg==}
cpu: [x64]
os: [win32]
[email protected]:
resolution: {integrity: sha512-nFZHfjYAaNbp3KapLvtORrPcWlL86/PYTXi5c2wN7ViaVhYhpS9oIZp6OfrMY83AecMibvnJ1fArefdOaagHtg==}
engines: {node: '>=18.*'}
hasBin: true
snapshots:
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
'@pnpm/[email protected]':
optional: true
[email protected]:
optionalDependencies:
'@pnpm/exe.android-arm64': 12.7.0
'@pnpm/exe.android-x64': 12.7.0
'@pnpm/exe.darwin-arm64': 12.7.0
'@pnpm/exe.darwin-x64': 12.7.0
'@pnpm/exe.freebsd-x64': 12.7.0
'@pnpm/exe.linux-arm64': 12.7.0
'@pnpm/exe.linux-arm64-musl': 12.7.0
'@pnpm/exe.linux-ppc64': 12.7.0
'@pnpm/exe.linux-riscv64': 12.7.0
'@pnpm/exe.linux-s390x': 12.7.0
'@pnpm/exe.linux-x64': 12.7.0
'@pnpm/exe.linux-x64-musl': 12.7.0
'@pnpm/exe.win32-arm64': 12.7.0
'@pnpm/exe.win32-x64': 12.7.0
---
lockfileVersion: '9.0'
settings:
@@ -5514,7 +5672,6 @@ packages:
[email protected]:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
[email protected]:
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+38 -38
View File
@@ -8,46 +8,46 @@
/** Drawn on the light palette */
export const HA_MAP_COLORS = {
background: "#f4efe6",
land: "#f4efe6",
water: "#bcd9e8",
labelWater: "#5b86a3",
natureWood: "#cfe1bf",
natureGrass: "#e2ecda",
naturePark: "#cfe1bf",
natureLeisure: "#e2ecda",
natureAgriculture: "#e8eee2",
natureWetland: "#e5ebe0",
natureSand: "#f1eadf",
natureRock: "#f1eadf",
glacier: "#f9f6f1",
areaResidential: "#efe8dc",
areaCommercial: "#f4e1db",
areaIndustrial: "#ede5ce",
areaWaste: "#ece3d5",
areaBurial: "#efe8dc",
siteParking: "#ede6d8",
siteSports: "#cfe1bf26",
building: "#e7ddca",
buildingBg: "#dcccb2",
background: "#f5efe5",
land: "#f5efe5",
water: "#b8daec",
labelWater: "#5487aa",
natureWood: "#cfe4bc",
natureGrass: "#e2eed8",
naturePark: "#cfe4bc",
natureLeisure: "#e2eed8",
natureAgriculture: "#e8efe1",
natureWetland: "#e5ecdf",
natureSand: "#f3eadd",
natureRock: "#f3eadd",
glacier: "#faf6f0",
areaResidential: "#f1e9da",
areaCommercial: "#f7e0d8",
areaIndustrial: "#f0e7cb",
areaWaste: "#eee3d3",
areaBurial: "#f1e9da",
siteParking: "#efe7d6",
siteSports: "#cfe4bc26",
building: "#eadec7",
buildingBg: "#e0cdae",
roadStreet: "#fcfaf8",
roadStreetBg: "#e6dac7",
roadTrunk: "#fbe6c2",
roadTrunkBg: "#e5cea5",
roadMotorway: "#ebd4ab",
roadMotorwayBg: "#deba78",
transitRail: "#e9dfce",
transitSubway: "#eae1d1",
transitCycle: "#f2ede3",
transitFoot: "#f2ede3",
boundary: "#d6c3a4",
boundaryDisputed: "#e2d6c0",
label: "#4a463d",
roadStreetBg: "#e9dbc4",
roadTrunk: "#ffe7be",
roadTrunkBg: "#ebd09f",
roadMotorway: "#f1d6a5",
roadMotorwayBg: "#e8bd6e",
transitRail: "#ece0cb",
transitSubway: "#ece2cf",
transitCycle: "#f3eee2",
transitFoot: "#f3eee2",
boundary: "#dbc49f",
boundaryDisputed: "#e5d7bd",
label: "#4b473c",
labelHalo: "#faf8f5cc",
labelSymbol: "#8d8676",
labelPoi: "#8d867666",
labelShield: "#faf8f4",
labelHousenumber: "#8d86764d",
labelSymbol: "#8f8774",
labelPoi: "#8f877466",
labelShield: "#fbf8f3",
labelHousenumber: "#8f87744d",
};
/** Drawn on the dark palette, in full: a light value left standing here would
+2 -2
View File
@@ -56,8 +56,8 @@ export class MockBaseEntity {
this.areaId = input.area_id;
this.deviceId = input.device_id;
this.platform = input.platform;
this.lastChanged = randomTime();
this.lastUpdated = randomTime();
this.lastChanged = input.last_changed ?? randomTime();
this.lastUpdated = input.last_changed ?? randomTime();
const attributes: EntityAttributes = input.attributes || {};
+27
View File
@@ -0,0 +1,27 @@
import { MockBaseEntity } from "./base-entity";
import type { EntityAttributes } from "./types";
const LOCATION_ATTRIBUTES = [
"latitude",
"longitude",
"gps_accuracy",
"radius",
"passive",
"persons",
"editable",
"source",
"source_type",
"in_zones",
];
export class MockLocationEntity extends MockBaseEntity {
protected _getStateAttributes(): EntityAttributes {
const stateAttrs: EntityAttributes = {};
for (const key of LOCATION_ATTRIBUTES) {
if (key in this.attributes) {
stateAttrs[key] = this.attributes[key];
}
}
return stateAttrs;
}
}
+4
View File
@@ -11,6 +11,7 @@ import { MockLawnMowerEntity } from "./lawn-mower-entity";
import { MockInputSelectEntity } from "./input-select-entity";
import { MockInputTextEntity } from "./input-text-entity";
import { MockLightEntity } from "./light-entity";
import { MockLocationEntity } from "./location-entity";
import { MockLockEntity } from "./lock-entity";
import { MockMediaPlayerEntity } from "./media-player-entity";
import { MockToggleEntity } from "./toggle-entity";
@@ -25,6 +26,7 @@ const TYPES: Record<string, EntityConstructor> = {
alarm_control_panel: MockAlarmControlPanelEntity,
climate: MockClimateEntity,
cover: MockCoverEntity,
device_tracker: MockLocationEntity,
fan: MockFanEntity,
group: MockGroupEntity,
humidifier: MockHumidifierEntity,
@@ -36,10 +38,12 @@ const TYPES: Record<string, EntityConstructor> = {
light: MockLightEntity,
lock: MockLockEntity,
media_player: MockMediaPlayerEntity,
person: MockLocationEntity,
switch: MockToggleEntity,
vacuum: MockVacuumEntity,
valve: MockValveEntity,
water_heater: MockWaterHeaterEntity,
zone: MockLocationEntity,
};
export const getEntity = (input: EntityInput): MockBaseEntity => {
+2
View File
@@ -17,6 +17,8 @@ export type EntityInput = Pick<
device_id?: string;
/** Integration that provides the entity, defaults to "demo" */
platform?: string;
/** When the state last changed, a random recent time by default */
last_changed?: string;
};
/**