Skip to content

Commit 008ad75

Browse files
authored
Merge pull request #329 from timkpaine/tkp/spaday-parity
Match legacy defaults, apply theme
2 parents c54a4de + eea749a commit 008ad75

3 files changed

Lines changed: 76 additions & 6 deletions

File tree

csp_gateway/server/modules/web/perspective.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ def ui(self, app: "GatewayUI") -> None:
750750
tables=list(tables.keys()),
751751
default_tables=self._select_default_layout_tables(tables),
752752
layouts=layouts,
753+
schemas=tables,
753754
),
754755
)
755756
default_view = self.default_layout or "__default__"

csp_gateway/server/web/spaday_ui.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,20 @@ def perspective_panel(
284284
tables: list[str] | None = None,
285285
default_tables: list[str] | None = None,
286286
layouts: dict[str, str] | None = None,
287+
schemas: dict[str, dict[str, str]] | None = None,
287288
) -> Any:
288289
"""A Perspective workspace panel (the primary data view), bound to the theme + `view` state.
289290
290291
Data rides Perspective's own websocket at ``route``; the panel only carries the workspace
291292
layout/theme config. ``default_tables`` are the ones the generated layout opens, defaulting
292-
to all of ``tables``. Add it to `Region.MAIN`.
293+
to all of ``tables``. ``schemas`` (table name -> column name -> type) lets the generated
294+
layout apply per-table defaults (timestamp sort, hidden id column). Add it to `Region.MAIN`.
293295
"""
294296
tables = list(tables or [])
295-
default_layout = self._default_layout(list(default_tables) if default_tables is not None else tables)
297+
default_layout = self._default_layout(
298+
list(default_tables) if default_tables is not None else tables,
299+
schemas=schemas,
300+
)
296301
layout_expr: Any = default_layout
297302
for name, layout_json in (layouts or {}).items():
298303
try:
@@ -448,13 +453,23 @@ def _form_overrides(overrides: dict[str, dict[str, Any]], form_field_cls: Any) -
448453
return result
449454

450455
@staticmethod
451-
def _default_layout(tables: list[str]) -> dict[str, Any]:
452-
"""A perspective workspace config that shows every table in its own datagrid tab."""
456+
def _default_layout(tables: list[str], schemas: dict[str, dict[str, str]] | None = None) -> dict[str, Any]:
457+
"""A perspective workspace config that shows every table in its own datagrid tab.
458+
459+
With ``schemas`` (table name -> column name -> type), panels match the legacy UI's defaults:
460+
sorted by ``timestamp`` descending when the table has one, and the ``id`` column hidden.
461+
"""
453462
panels: dict[str, Any] = {}
454463
panel_ids: list[str] = []
455464
for i, table in enumerate(tables):
456465
panel_id = f"CSP_GATEWAY_{i}"
457-
panels[panel_id] = {"table": table, "plugin": "Datagrid", "title": table}
466+
panel: dict[str, Any] = {"table": table, "plugin": "Datagrid", "title": table}
467+
schema = (schemas or {}).get(table) or {}
468+
if "timestamp" in schema:
469+
panel["sort"] = [["timestamp", "desc"]]
470+
if "id" in schema:
471+
panel["columns"] = [column for column in schema if column != "id"]
472+
panels[panel_id] = panel
458473
panel_ids.append(panel_id)
459474
return {
460475
"layout": {"type": "tab-layout", "tabs": panel_ids, "selected": 0},
@@ -773,7 +788,11 @@ def mount(self) -> None:
773788
layout="installed",
774789
wire=wire,
775790
routes=routes,
776-
store={"dark": False, **self._store_seeds},
791+
# `dark` matches the browser preference at boot (client-evaluated, like the legacy UI's
792+
# prefers-color-scheme detection), and a manual toggle is persisted per browser and takes
793+
# precedence on later loads.
794+
store={"dark": Js('matchMedia("(prefers-color-scheme: dark)").matches'), **self._store_seeds},
795+
persist={"dark": "csp-gateway:dark"},
777796
# Emitted after the component packages' own CSS, so a custom stylesheet can override the
778797
# shell palette, and before `head`, which carries only document resets.
779798
stylesheets=custom_css,

csp_gateway/tests/server/web/test_spaday_ui.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,56 @@ def test_one_datagrid_panel_per_table(self):
250250
"CSP_GATEWAY_1": {"table": "fills", "plugin": "Datagrid", "title": "fills"},
251251
}
252252

253+
def test_schemas_add_legacy_parity_sort_and_columns(self):
254+
# Legacy-UI parity: timestamp sorts descending and the id column is hidden.
255+
from csp_gateway.server.web.spaday_ui import GatewayUI
256+
257+
layout = GatewayUI._default_layout(
258+
["orders"],
259+
schemas={"orders": {"id": "string", "timestamp": "datetime", "price": "float"}},
260+
)
261+
panel = layout["panels"]["CSP_GATEWAY_0"]
262+
assert panel["sort"] == [["timestamp", "desc"]]
263+
assert panel["columns"] == ["timestamp", "price"]
264+
265+
def test_schema_without_timestamp_or_id_stays_bare(self):
266+
from csp_gateway.server.web.spaday_ui import GatewayUI
267+
268+
layout = GatewayUI._default_layout(["orders"], schemas={"orders": {"price": "float"}})
269+
panel = layout["panels"]["CSP_GATEWAY_0"]
270+
assert "sort" not in panel
271+
assert "columns" not in panel
272+
273+
274+
class TestDarkBoot:
275+
"""The page seeds `dark` from the browser's prefers-color-scheme, like the legacy UI."""
276+
277+
@pytest.fixture(scope="class")
278+
def gateway(self, free_port):
279+
return Gateway(
280+
modules=[ExampleModule(), MountRestRoutes(force_mount_all=True)],
281+
channels=ExampleChannels(),
282+
settings=GatewaySettings(PORT=free_port, UI_PROVIDER="spaday"),
283+
)
284+
285+
@pytest.fixture(scope="class")
286+
def client(self, gateway):
287+
gateway.start(rest=True, ui=True, _in_test=True)
288+
try:
289+
yield TestClient(gateway.web_app.get_fastapi())
290+
finally:
291+
gateway.stop()
292+
293+
def test_dark_seed_is_client_evaluated(self, client: TestClient):
294+
page = client.get("/").text
295+
assert '"dark": (matchMedia("(prefers-color-scheme: dark)").matches)' in page
296+
297+
def test_dark_choice_persists_across_reloads(self, client: TestClient):
298+
# A manual toggle is stored and overrides the browser preference on the next load (legacy parity).
299+
page = client.get("/").text
300+
assert 'localStorage.getItem("csp-gateway:dark")' in page
301+
assert 'store.subscribe("dark", (v) => { try { localStorage.setItem("csp-gateway:dark", JSON.stringify(v)); } catch {} });' in page
302+
253303

254304
class TestSpadayPerspectiveLayoutActions:
255305
@pytest.fixture(scope="class")

0 commit comments

Comments
 (0)