Draw a tile on the editor's mockup the way the screen does: the CYD shows the whole tile again (0.2.76)
Both boards draw a normal tile with the icon on the left and the name and value beside it; the Vue editor stacked icon, name and value in one column. On a CYD mockup a tile is 47 px high and that stack needed 58 px (63 for a watch card), so the value line was cut in half and a big value hid the name. A single tile is now a row (.head: icon + .tx), a watch card puts the name row on top and the number under it, and the small slider runs underneath; on a CYD a tile with a slider uses the screen's 4 px padding and 8 px slider. The Guition mockup follows the screen the same way. App 0.2.76, firmware stays 0.2.63. Test results in docs/TEST_RESULTS_0276.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -154,14 +154,18 @@ async function onKey(e: KeyboardEvent) {
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="ic mdi" :class="{ lit: isOn }">{{ glyph(tileIconCp(tile)) }}</span>
|
||||
<span class="lead">
|
||||
<span v-if="display === 'watch'" class="big">{{ bigValue }}<small v-if="unit && !gone">{{ unit }}</small></span>
|
||||
<span class="nm">{{ name }}</span>
|
||||
<span v-if="goesTo" class="goto">Page {{ goesTo }} ›</span>
|
||||
<span v-else-if="display !== 'watch' && status" class="st" :class="{ off: gone }">{{ status }}</span>
|
||||
<span v-if="tile.options?.inline === 'slider'" class="mini-slider" :style="sliderStyle"></span>
|
||||
<!-- As the screen draws it: the icon on the left, the name and the value beside it. A watch
|
||||
card puts the name on top and the big value under it; the small slider runs underneath. -->
|
||||
<span class="head" :class="{ top: display === 'watch' }">
|
||||
<span class="ic mdi" :class="{ lit: isOn }">{{ glyph(tileIconCp(tile)) }}</span>
|
||||
<span class="tx">
|
||||
<span class="nm">{{ name }}</span>
|
||||
<span v-if="goesTo" class="goto">Page {{ goesTo }} ›</span>
|
||||
<span v-else-if="display !== 'watch' && status" class="st" :class="{ off: gone }">{{ status }}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="display === 'watch'" class="big">{{ bigValue }}<small v-if="unit && !gone">{{ unit }}</small></span>
|
||||
<span v-if="tile.options?.inline === 'slider'" class="mini-slider" :style="sliderStyle"></span>
|
||||
</template>
|
||||
<button v-if="live" type="button" class="remove" title="Remove tile" :aria-label="`Remove ${name}`" @click.stop="removeTile(tile)">✕</button>
|
||||
</div>
|
||||
|
||||
@@ -149,12 +149,18 @@ a:hover { text-decoration: underline; }
|
||||
.tile .ic { font-size: 18px; line-height: 1; color: #1b1b1b; flex: none; }
|
||||
.tile .lead { display: grid; gap: 2px; min-width: 0; flex: 1; align-content: end; }
|
||||
.tile.wide .lead { display: flex; align-items: center; gap: 8px; align-content: initial; }
|
||||
.tile.wide .lead .tx { display: grid; min-width: 0; gap: 1px; }
|
||||
.tile .tx { display: grid; min-width: 0; gap: 1px; }
|
||||
.tile .head { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
|
||||
.tile .head.top { flex: none; }
|
||||
.tile .nm { font-size: 11.5px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.tile .st { font-size: 10.5px; color: #5a5f66; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.device.cyd .tile { padding: 6px 7px; }
|
||||
.device.cyd .tile .nm { font-size: 10.5px; }
|
||||
.device.cyd .tile .ic { font-size: 15px; }
|
||||
.device.cyd .tile .head { gap: 6px; }
|
||||
/* The screen's own numbers on its short tiles: 4 px above and below, an 8 px slider. */
|
||||
.device.cyd .tile:has(> .mini-slider) { padding-block: 4px; gap: 3px; }
|
||||
.device.cyd .tile .mini-slider { height: 8px; }
|
||||
.tile .mini-slider { height: 10px; border-radius: 5px; background: linear-gradient(to right, #ffbf38 60%, #fff1d3 60%); flex: none; width: 100%; }
|
||||
.tile.wide .mini-slider { width: 70px; }
|
||||
.tile .ctl { flex: none; display: flex; align-items: center; gap: 4px; color: #1b1b1b; }
|
||||
|
||||
@@ -65,6 +65,21 @@ describe("TileCard", () => {
|
||||
expect(gone.find(".st").text()).toBe("Unavailable");
|
||||
expect(gone.find(".st").classes()).toContain("off");
|
||||
});
|
||||
it("draws a single tile as the screen does: the icon left, the name and value beside it", () => {
|
||||
state.liveStates["sensor.t"] = { state: "1249", word: null, a: { unit_of_measurement: "W" } };
|
||||
const plain = placed({ entity: "sensor.t", name: "Power", slot: 0 });
|
||||
expect(plain.find(".head > .ic").exists()).toBe(true);
|
||||
expect(plain.find(".head > .tx > .nm").text()).toBe("Power");
|
||||
expect(plain.find(".head > .tx > .st").text()).toBe("1249 W");
|
||||
// A watch card keeps the name next to the icon and puts the big value underneath.
|
||||
const watch = placed({ entity: "sensor.t", name: "Power", slot: 1, options: { display: "watch" } });
|
||||
expect(watch.find(".head").classes()).toContain("top");
|
||||
expect(watch.find(".head .big").exists()).toBe(false);
|
||||
expect(watch.find(".head + .big").text()).toBe("1249W");
|
||||
state.liveStates["light.a"] = { state: "on", word: "On", a: { brightness: 255 } };
|
||||
const lamp = placed({ entity: "light.a", name: "", slot: 2, options: { inline: "slider" } });
|
||||
expect(lamp.find(".head + .mini-slider").exists()).toBe(true);
|
||||
});
|
||||
it("shows the display name when Home Assistant has no value, and nothing for a scene", () => {
|
||||
const graph = placed({ entity: "sensor.x", name: "Unknown sensor", slot: 0, options: { display: "graph" } });
|
||||
expect(graph.find(".st").text()).toBe("graph");
|
||||
|
||||
Reference in New Issue
Block a user