Free drag & drop with fixed positions, empty cells and kept pages (0.2.31)

Tiles get an absolute grid slot instead of a packed order: a tile lands exactly
where it is dropped, dropping on another tile swaps the two, everything else
stays put, and empty cells and empty pages persist. The mockup previews the
result while dragging, a new page waits below the last one, "Pagina toevoegen"
keeps an empty page, empty cells accept the next tile from the picker, and the
mouse no longer selects text while dragging.

Storage and protocol stay version 1: `tiles[].slot` (0-47) and `pages` (1-8)
are additive, the layout message carries `slots` and `pages`. Firmware 0.2.26
places tiles on those slots (`place()` next to `pack()`); older firmware packs
in order as before and the editor says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-14 13:00:39 +02:00
parent ad0385d60f
commit 95cdfa30cf
19 changed files with 676 additions and 140 deletions
+7 -4
View File
@@ -90,7 +90,9 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
layout={'title':'Mijn huis','tiles':[{'entity':'light.a','name':'Eigen naam'}]}
m.save('text.screen',layout)
fresh=self.setup_manager(path)
self.assertEqual(fresh.layouts['text.screen'],layout)
# Stored layouts always carry a grid position; a save without one packs in order.
self.assertEqual(fresh.layouts['text.screen'],{**layout,'tiles':[{**layout['tiles'][0],'slot':0}]})
layout=fresh.layouts['text.screen']
await fresh.sync_one('text.screen',layout)
self.assertEqual(len(fresh.ha.messages),2)
await fresh.sync_one('text.screen',layout)
@@ -109,7 +111,8 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
legacy={'title':'Thuis','tiles':[{'entity':'light.a','name':'Mijn lamp'}]}
path.write_text(json.dumps({'version':1,'screens':{'text.screen':legacy}}))
m=self.setup_manager(path)
self.assertEqual(m.layouts['text.screen'],legacy)
stored={**legacy,'tiles':[{**legacy['tiles'][0],'slot':0}]}
self.assertEqual(m.layouts['text.screen'],stored)
settings=validate_settings({'brightness':55,'standby_seconds':1200})
m.save('text.screen',{**legacy,'settings':settings})
fresh=self.setup_manager(path)
@@ -117,7 +120,7 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
fresh.save('text.screen',{**legacy,'title':'Andere titel'})
saved=fresh.layouts['text.screen']
self.assertEqual(saved['settings'],settings)
self.assertEqual(saved['tiles'],legacy['tiles'])
self.assertEqual(saved['tiles'],stored['tiles'])
before=path.read_bytes()
with self.assertRaises(ValueError): fresh.save('text.screen',{**saved,'settings':{'brightness':0}})
self.assertEqual(path.read_bytes(),before)
@@ -137,7 +140,7 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
await original(inbox,message)
m.save(inbox,{'title':'Leeg','tiles':[]})
m.ha.send=interrupt
await m.sync_one('text.screen',layout)
await m.sync_one('text.screen',m.layouts['text.screen'])
self.assertEqual(len(m.ha.messages),1)
self.assertNotIn('text.screen',m.sent)