Coverage for src / lexigram / ui / molecules / realtime.py: 34%

29 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1"""Real-time UI components for Lexigram Admin.""" 

2 

3from __future__ import annotations 

4 

5from typing import Any 

6 

7from lexigram.ui.core.base import Component, el 

8from lexigram.ui.htmx.attrs import hx_get, hx_swap, hx_target, hx_trigger 

9 

10 

11class RealTimeFeed(Component): 

12 """A component that polls or uses SSE to update its content in real-time. 

13 

14 Example: 

15 ```python 

16 feed = RealTimeFeed( 

17 url="/admin//updates", 

18 interval="5s", 

19 content=el("p", "Waiting for updates...") 

20 ) 

21 ``` 

22 """ 

23 

24 def __init__( 

25 self, 

26 url: str, 

27 interval: str | None = "10s", 

28 use_sse: bool = False, 

29 content: Any = None, 

30 **props: Any, 

31 ) -> None: 

32 super().__init__(**props) 

33 self.url = url 

34 self.interval = interval 

35 self.use_sse = use_sse 

36 self.initial_content = content or el( 

37 "div", 

38 {"class": "animate-pulse h-4 bg-muted rounded w-3/4"}, 

39 ) 

40 

41 def render(self) -> Any: 

42 attrs = { 

43 "class": "real-time-feed", 

44 "id": self.props.get("id", "feed-" + str(id(self))), 

45 } 

46 

47 if self.use_sse: 

48 attrs.update( 

49 {"hx-ext": "sse", "sse-connect": self.url, "sse-swap": "message"}, 

50 ) 

51 elif self.interval: 

52 attrs.update(hx_get(self.url)) 

53 attrs.update(hx_trigger(f"every {self.interval}")) 

54 attrs.update(hx_target("this")) 

55 attrs.update(hx_swap("innerHTML")) 

56 

57 return el("div", attrs, self.initial_content) 

58 

59 

60class LiveCounter(Component): 

61 """A simple counter that updates in real-time.""" 

62 

63 def __init__(self, label: str, url: str, interval: str = "5s", **props: Any): 

64 super().__init__(**props) 

65 self.label = label 

66 self.url = url 

67 self.interval = interval 

68 

69 def render(self) -> Any: 

70 return el( 

71 "div", 

72 { 

73 "class": "p-4 bg-card rounded-lg shadow sm:p-6 transition-colors duration-300", 

74 }, 

75 el( 

76 "dt", 

77 {"class": "text-sm font-medium text-muted-foreground truncate"}, 

78 self.label, 

79 ), 

80 el( 

81 "dd", 

82 {"class": "mt-1 text-3xl font-semibold text-foreground"}, 

83 RealTimeFeed(url=self.url, interval=self.interval, content="--"), 

84 ), 

85 )