Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

tui: remove circular imports

+25 -40
+3 -8
tui/app.py
··· 1 1 import os 2 2 3 3 import httpx 4 - from platformdirs import user_data_dir 5 4 from textual.app import App, ComposeResult 6 5 from textual.binding import Binding 7 6 ··· 10 9 from textual.screen import Screen 11 10 from textual.widgets import Button, Footer, Static 12 11 12 + from tui.paths import DATA_DIR 13 + from tui.screens.activity import ActivityScreen 13 14 from tui.screens.home import HomeScreen 15 + from tui.screens.login import LoginScreen 14 16 15 17 16 18 class LogoutConfirmScreen(Screen): ··· 49 51 self.app.pop_screen() 50 52 51 53 52 - DATA_DIR = os.environ.get("ATBBS_DATA_DIR", user_data_dir("atbbs")) 53 - 54 - 55 54 class AtbbsApp(App): 56 55 TITLE = "@bbs" 57 56 CSS_PATH = "app.tcss" ··· 99 98 if self.user_session: 100 99 self.push_screen(LogoutConfirmScreen()) 101 100 else: 102 - from tui.screens.login import LoginScreen 103 - 104 101 self.push_screen(LoginScreen()) 105 102 106 103 def do_logout(self) -> None: ··· 116 113 if not self.user_session: 117 114 self.notify("Log in to see your messages.", severity="warning") 118 115 return 119 - from tui.screens.activity import ActivityScreen 120 - 121 116 self.push_screen(ActivityScreen()) 122 117 123 118 def action_refresh(self) -> None:
+7
tui/paths.py
··· 1 + """Filesystem paths for the TUI.""" 2 + 3 + import os 4 + 5 + from platformdirs import user_data_dir 6 + 7 + DATA_DIR = os.environ.get("ATBBS_DATA_DIR", user_data_dir("atbbs"))
+2 -4
tui/screens/activity.py
··· 4 4 from textual.widgets import Footer, Static 5 5 from textual import work 6 6 7 + from tui.screens.thread import ThreadScreen 8 + from tui.widgets.breadcrumb import Breadcrumb 7 9 from tui.widgets.post import Post 8 10 9 11 ··· 18 20 self._items: list[dict] = [] 19 21 20 22 def compose(self) -> ComposeResult: 21 - from tui.widgets.breadcrumb import Breadcrumb 22 - 23 23 yield Breadcrumb( 24 24 ("@bbs", 1), 25 25 ("inbox", 0), ··· 89 89 updated_at=rec.value.get("updatedAt"), 90 90 attachments=rec.value.get("attachments"), 91 91 ) 92 - from tui.screens.thread import ThreadScreen 93 - 94 92 self.app.push_screen(ThreadScreen(bbs, handle, thread)) 95 93 except Exception: 96 94 self.notify("Could not open thread.", severity="error")
+3 -6
tui/screens/board.py
··· 6 6 7 7 from core.models import BBS, Board 8 8 from core.records import hydrate_threads as fetch_threads 9 + from tui.screens.compose import ComposeThreadScreen 10 + from tui.screens.thread import ThreadScreen 9 11 from tui.util import require_session 10 12 from tui.util import format_datetime 13 + from tui.widgets.breadcrumb import Breadcrumb 11 14 12 15 13 16 class BoardScreen(Screen): ··· 32 35 self.page = 0 33 36 34 37 def compose(self) -> ComposeResult: 35 - from tui.widgets.breadcrumb import Breadcrumb 36 - 37 38 yield Breadcrumb( 38 39 ("@bbs", 2), 39 40 (self.bbs.site.name, 1), ··· 94 95 # Find thread by URI 95 96 thread = next((t for t in self.threads if t.uri == uri), None) 96 97 if thread: 97 - from tui.screens.thread import ThreadScreen 98 - 99 98 self.app.push_screen(ThreadScreen(self.bbs, self.handle, thread)) 100 99 101 100 def refresh_data(self) -> None: ··· 111 110 def action_new_thread(self) -> None: 112 111 if not require_session(self): 113 112 return 114 - from tui.screens.compose import ComposeThreadScreen 115 - 116 113 self.app.push_screen(ComposeThreadScreen(self.bbs, self.handle, self.board))
+1 -4
tui/screens/compose.py
··· 12 12 from core.models import AuthError 13 13 from core.records import create_thread_record, create_reply_record, upload_blob 14 14 from tui.util import require_session 15 + from tui.widgets.breadcrumb import Breadcrumb 15 16 16 17 17 18 async def _upload_file(screen, file_path: str, session: dict) -> list[dict] | None: ··· 53 54 self.board = board 54 55 55 56 def compose(self) -> ComposeResult: 56 - from tui.widgets.breadcrumb import Breadcrumb 57 - 58 57 yield Breadcrumb( 59 58 ("@bbs", 3), 60 59 (self.bbs.site.name, 2), ··· 132 131 self.thread = thread 133 132 134 133 def compose(self) -> ComposeResult: 135 - from tui.widgets.breadcrumb import Breadcrumb 136 - 137 134 yield Breadcrumb( 138 135 ("@bbs", 3), 139 136 (self.bbs.site.name, 2),
+1 -2
tui/screens/home.py
··· 10 10 from core.models import BBSNotFoundError, NetworkError, NoBBSError 11 11 from core.resolver import resolve_bbs 12 12 from core.slingshot import resolve_identities_batch 13 + from tui.screens.site import SiteScreen 13 14 14 15 15 16 class HomeScreen(Screen): ··· 95 96 if session and bbs.site.is_banned(session.get("did")): 96 97 self.notify("You have been banned from this BBS.", severity="error") 97 98 return 98 - 99 - from tui.screens.site import SiteScreen 100 99 101 100 self.app.push_screen(SiteScreen(bbs, handle)) 102 101 self.query_one("#handle-input", Input).value = ""
+2 -4
tui/screens/login.py
··· 22 22 23 23 24 24 from core.lexicon import OAUTH_SCOPE 25 + from tui.paths import DATA_DIR 26 + from tui.widgets.breadcrumb import Breadcrumb 25 27 26 28 CALLBACK_PORT = 23847 27 29 ··· 30 32 BINDINGS = [("escape", "app.pop_screen", "back")] 31 33 32 34 def compose(self) -> ComposeResult: 33 - from tui.widgets.breadcrumb import Breadcrumb 34 - 35 35 yield Breadcrumb( 36 36 ("@bbs", 1), 37 37 ("log in", 0), ··· 71 71 return 72 72 73 73 # Load client secrets from TUI data dir 74 - from tui.app import DATA_DIR 75 - 76 74 secrets = load_secrets(DATA_DIR) 77 75 client_secret_jwk = json.loads(secrets["client_secret_jwk"]) 78 76
+1 -2
tui/screens/news.py
··· 4 4 from textual.widgets import Footer 5 5 6 6 from core.models import BBS, News 7 + from tui.widgets.breadcrumb import Breadcrumb 7 8 from tui.widgets.post import Post 8 9 9 10 ··· 17 18 self.news = news 18 19 19 20 def compose(self) -> ComposeResult: 20 - from tui.widgets.breadcrumb import Breadcrumb 21 - 22 21 yield Breadcrumb( 23 22 ("@bbs", 2), 24 23 (self.bbs.site.name, 1),
+3 -6
tui/screens/site.py
··· 6 6 7 7 from core.models import BBS 8 8 from core.resolver import resolve_bbs 9 + from tui.screens.board import BoardScreen 10 + from tui.screens.news import NewsScreen 9 11 from tui.util import format_datetime 12 + from tui.widgets.breadcrumb import Breadcrumb 10 13 11 14 12 15 class SiteScreen(Screen): ··· 18 21 self.handle = handle 19 22 20 23 def compose(self) -> ComposeResult: 21 - from tui.widgets.breadcrumb import Breadcrumb 22 - 23 24 yield Breadcrumb( 24 25 ("@bbs", 1), 25 26 (self.handle, 0), ··· 75 76 slug = event.item.name 76 77 board = next((b for b in self.bbs.site.boards if b.slug == slug), None) 77 78 if board: 78 - from tui.screens.board import BoardScreen 79 - 80 79 self.app.push_screen(BoardScreen(self.bbs, self.handle, board)) 81 80 elif event.list_view.id == "news-list": 82 81 idx = int(event.item.name) 83 82 if 0 <= idx < len(self.bbs.news): 84 - from tui.screens.news import NewsScreen 85 - 86 83 self.app.push_screen( 87 84 NewsScreen(self.bbs, self.handle, self.bbs.news[idx]) 88 85 )
+2 -4
tui/screens/thread.py
··· 8 8 from core import lexicon 9 9 from core.models import AtUri, AuthError, BBS, Thread 10 10 from core.records import delete_record, hydrate_replies as fetch_replies 11 + from tui.screens.compose import ComposeReplyScreen 11 12 from tui.util import require_session 13 + from tui.widgets.breadcrumb import Breadcrumb 12 14 from tui.widgets.post import Post 13 15 14 16 ··· 39 41 board_name = next( 40 42 (b.name for b in self.bbs.site.boards if b.slug == board_slug), board_slug 41 43 ) 42 - from tui.widgets.breadcrumb import Breadcrumb 43 - 44 44 yield Breadcrumb( 45 45 ("@bbs", 3), 46 46 (self.bbs.site.name, 2), ··· 167 167 and focused.record_uri 168 168 ): 169 169 quote = self._replies_map.get(focused.record_uri) 170 - 171 - from tui.screens.compose import ComposeReplyScreen 172 170 173 171 self.app.push_screen( 174 172 ComposeReplyScreen(self.bbs, self.handle, self.thread, quote=quote)