Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions assets/zone/icons.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The svg file names in both Sketch as in this file should use - not _

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 74a9333: all zone SVG filenames and manifest references now use hyphens instead of underscores.

@nozols nozols May 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HarwinBorger we cannot just change these file paths, that is a breaking change for consumers of this library. I don't think that's worth it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed with @HarwinBorger privately on Slack, I agree that the naming is currently inconsistent. However, I also agree with @nozols that changing this would be a breaking change and is out of scope for this PR.

If we want to make the naming more consistent, we should first inventory which consumers rely on the current naming and check whether we can change this safely in separate PRs.

@HarwinBorger could you revert your changes for this PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these icons like two weeks ago so I doubt anyone uses them at this point.

Just bump a major version :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"defaultIconId": "default",
"icons": [
{ "id": "firstFloor", "fileName": "first-floor.svg" },
{ "id": "groundFloor", "fileName": "ground-floor.svg" },
{ "id": "officeBuildings", "fileName": "office-buildings.svg" },
{ "id": "singleFloor", "fileName": "single-floor.svg" },
{ "id": "topFloor", "fileName": "top-floor.svg" },
{ "id": "default", "fileName": "zone.svg" },
{ "id": "home", "fileName": "home.svg" },
{ "id": "floor", "fileName": "floor.svg" },
{ "id": "basement", "fileName": "basement.svg" },
{ "id": "attic", "fileName": "attic.svg" },
{ "id": "roofSolar", "fileName": "roof-solar-panels.svg" },
{ "id": "garage", "fileName": "garage.svg" },
{ "id": "pool", "fileName": "pool.svg" },
{ "id": "garden", "fileName": "garden.svg" },
{ "id": "gardenShed", "fileName": "garden-shed.svg" },
{ "id": "terrace", "fileName": "terrace.svg" },
{ "id": "carport", "fileName": "carport.svg" },
{ "id": "hallway", "fileName": "hallway.svg" },
{ "id": "livingRoom", "fileName": "living-room.svg" },
{ "id": "kitchen", "fileName": "kitchen.svg" },
{ "id": "diningRoom", "fileName": "dining-room.svg" },
{ "id": "bedroomSingle", "fileName": "bedroom.svg" },
{ "id": "bedroomDouble", "fileName": "master-bedroom.svg" },
{ "id": "bedroomKids", "fileName": "kinder-room.svg" },
{ "id": "bathroom", "fileName": "bathroom.svg" },
{ "id": "wardrobe", "fileName": "wardrobe.svg" },
{ "id": "office", "fileName": "office.svg" },
{ "id": "gym", "fileName": "gym.svg" },
{ "id": "toilet", "fileName": "toilet.svg" },
{ "id": "entrance", "fileName": "entrance-front-door.svg" },
{ "id": "studyRoom", "fileName": "study-room.svg" },
{ "id": "gameRoom", "fileName": "game-room.svg" },
{ "id": "utilitiesRoom", "fileName": "utilities-room.svg" },
{ "id": "recreationRoom", "fileName": "recreation-room.svg" },
{ "id": "tipi", "fileName": "tipi-tent.svg" },
{ "id": "doorClosed", "fileName": "door-closed.svg" },
{ "id": "lounge", "fileName": "lounge-chair.svg" },
{ "id": "fuseBox", "fileName": "lightning-bolt.svg" },
{ "id": "laundryRoom", "fileName": "laundry-basket.svg" },
{ "id": "phone", "fileName": "phone.svg" },
{ "id": "coffeeMachine", "fileName": "coffee-machine.svg" },
{ "id": "drinks", "fileName": "drinks.svg" },
{ "id": "pantry", "fileName": "pantry.svg" },
{ "id": "suitcase", "fileName": "suitcase.svg" },
{ "id": "door", "fileName": "door.svg" },
{ "id": "toothbrush", "fileName": "toothbrush.svg" },
{ "id": "sink", "fileName": "sink.svg" }
],
"legacyIcons": [
{ "id": "bed", "targetId": "bedroomDouble" },
{ "id": "bedroom", "targetId": "bedroomSingle" },
{ "id": "books", "targetId": "studyRoom" },
{ "id": "entranceFrontDoor", "targetId": "entrance" },
{ "id": "hallwayDoor", "targetId": "hallwayDoor", "fileName": "hallway-door.svg" },
{ "id": "kinderRoom", "targetId": "bedroomKids" },
{ "id": "living", "targetId": "livingRoom" },
{ "id": "masterBedroom", "targetId": "bedroomDouble" },
{ "id": "roof", "targetId": "attic" },
{ "id": "roofSolarPanels", "targetId": "roofSolar" },
{ "id": "shower", "targetId": "bathroom" },
{ "id": "stairs-down", "targetId": "basement" },
{ "id": "stairs-up", "targetId": "floor" },
{ "id": "tipiTent", "targetId": "tipi" }
]
}
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Energy = require('./lib/Energy');
const Media = require('./lib/Media');
const Signal = require('./lib/Signal');
const Util = require('./lib/Util');
const Zone = require('./lib/Zone');

module.exports.App = App;
module.exports.Capability = Capability;
Expand All @@ -15,6 +16,7 @@ module.exports.Energy = Energy;
module.exports.Media = Media;
module.exports.Signal = Signal;
module.exports.Util = Util;
module.exports.Zone = Zone;

/** @type {typeof Device.getClasses} */
module.exports.getDeviceClasses = Device.getClasses.bind(Device);
Expand Down Expand Up @@ -45,6 +47,11 @@ module.exports.getCurrencies = Energy.getCurrencies.bind(Energy);
/** @type {typeof Energy.getBatteries} */
module.exports.getBatteries = Energy.getBatteries.bind(Energy);

/** @type {typeof Zone.getIcons} */
module.exports.getZoneIcons = Zone.getIcons.bind(Zone);
/** @type {typeof Zone.resolveIcon} */
module.exports.resolveZoneIcon = Zone.resolveIcon.bind(Zone);

/** @typedef {import('./assets/app/schema').App} AppManifest */
/** @typedef {import('./assets/capability/schema').Capability} CapabilityDefinition */
/** @typedef {import('./assets/app/schema').ZigbeeFirmwareUpdates} ZigbeeFirmwareUpdates */
Expand Down
90 changes: 90 additions & 0 deletions lib/Zone/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';

const { join } = require('../../helpers');

/** @typedef {{ id: string, fileName: string }} ZoneIconManifestIcon */
/** @typedef {{ id: string, targetId: string, fileName?: string }} ZoneIconManifestLegacyIcon */
/** @typedef {{ defaultIconId: string, icons: ZoneIconManifestIcon[], legacyIcons: ZoneIconManifestLegacyIcon[] }} ZoneIconManifest */
/** @typedef {{ id: string, fileName: string, path?: string }} ZoneIconDescriptor */

/** @type {ZoneIconManifest} */
const ICONS_MANIFEST = require('../../assets/zone/icons.json');

const ICONS_PATH_SEGMENTS = [__dirname, '..', '..', 'assets', 'zone', 'icons'];
const DEFAULT_ICON_ID = ICONS_MANIFEST.defaultIconId;
const ACTIVE_ICONS = ICONS_MANIFEST.icons.reduce((obj, icon) => {
obj[icon.id] = icon.fileName;
return obj;
}, {});
const LEGACY_ICONS = ICONS_MANIFEST.legacyIcons.reduce((obj, icon) => {
obj[icon.id] = {
id: icon.targetId,
fileName: icon.fileName,
};
return obj;
}, {});

/** @type {ZoneIconDescriptor[] | undefined} */
let iconsCache;

class Zone {

/**
* @returns {ZoneIconDescriptor[]}
*/
static getIcons() {
if (iconsCache) return iconsCache;

iconsCache = ICONS_MANIFEST.icons.map(({ id, fileName }) => Zone._createIconDescriptor({
id,
fileName,
}));

return iconsCache;
}

/**
* @param {string | null | undefined} icon
* @returns {ZoneIconDescriptor}
*/
static resolveIcon(icon) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot is this the best name for the function?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept the name resolveIcon (current HEAD 74a9333) because it stays concise within Zone and aligns with the public resolveZoneIcon export; it resolves an icon id to the descriptor.

const legacyIcon = typeof icon === 'string' ? LEGACY_ICONS[icon] : undefined;
const id = legacyIcon ? legacyIcon.id : icon;
let fileName = legacyIcon ? legacyIcon.fileName : undefined;

if (!fileName && typeof id === 'string') {
fileName = ACTIVE_ICONS[id];
}

if (!fileName) {
return Zone._createIconDescriptor({
id: DEFAULT_ICON_ID,
fileName: ACTIVE_ICONS[DEFAULT_ICON_ID],
});
}

return Zone._createIconDescriptor({
id,
fileName,
});
}

static _createIconDescriptor({
id,
fileName,
}) {
const descriptor = {
Comment on lines +72 to +76
id,
fileName,
};

if (typeof join === 'function') {
descriptor.path = join(...ICONS_PATH_SEGMENTS, fileName);
}

return descriptor;
}

}

module.exports = Zone;
31 changes: 31 additions & 0 deletions test/zone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const assert = require('assert');
const fs = require('fs');

const HomeyLib = require('..');

describe('Zone icons', function() {
it('returns active zone icons', function() {
const icons = HomeyLib.getZoneIcons();

assert.strictEqual(icons.length, 47);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to check icons.length > 0 instead, then we don't have to update the tests if we add more icons.

assert.strictEqual(icons[0].id, 'firstFloor');
assert.strictEqual(icons[0].fileName, 'first-floor.svg');
assert.strictEqual(icons.some(icon => icon.id === 'hallwayDoor'), false);

for (const icon of icons) {
assert.strictEqual(typeof icon.id, 'string');
assert.strictEqual(typeof icon.fileName, 'string');
assert.strictEqual(typeof icon.path, 'string');
assert.strictEqual(fs.existsSync(icon.path), true);
}
});

it('resolves active, legacy, and unknown zone icons', function() {
assert.strictEqual(HomeyLib.resolveZoneIcon('default').fileName, 'zone.svg');
assert.strictEqual(HomeyLib.resolveZoneIcon('bed').id, 'bedroomDouble');
assert.strictEqual(HomeyLib.resolveZoneIcon('hallwayDoor').fileName, 'hallway-door.svg');
assert.strictEqual(HomeyLib.resolveZoneIcon('unknown').id, 'default');
});
});