import logging import os from enum import Enum LOG_FILE = 'textual.log' LOGGING_LEVEL = logging.DEBUG LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' # Clear Log File os.remove(LOG_FILE) logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT) import SubSonic from textual import on, events from textual.app import App, ComposeResult from textual.widgets import Footer, Header, OptionList, DataTable, Tabs, Tab, TabbedContent, TabPane from textual.widgets.option_list import Option u = 'pseudo' p = 'telegram9dock-MICHEL' conn = SubSonic.SubpyConn(u, p, "music.0x01fe.net") class Menus(Enum): MAIN = "MAIN" ARTISTS = "Artists" ARTISTSONGS = "ArtistSongs" ARTISTALBUMS = "ArtistAlbums" ALBUMS = "Albums" ALBUMSONGS = "AlbumSongs" class TextualSubpyPlayer(App): # BINDINGS = [ # ('backspace', 'menu_back()', 'Go back a menu') # ] artists: list[SubSonic.Artist] selectedArtist: SubSonic.Artist albums: list[SubSonic.Album] conn: SubSonic.SubpyConn currentMenu: Menus lastMenu: Menus last_menu_type: any def compose(self) -> ComposeResult: self.currentMenu = Menus.MAIN self.lastMenu = Menus.MAIN self.last_menu_type = None with TabbedContent(initial="Artists"): with TabPane("Artists", id="Artists"): yield OptionList(id="Artists") yield Footer() # yield OptionList( # Option("Browse Artists", id="BrowseArtists"), # Option("Browse Albums"), # Option("Browse Songs"), # Option("Browse Playlists"), # id=Menus.MAIN.value # ) # def on_mount(self) -> None: # self.query_one(Tabs).allow_focus(False) # async def action_menu_back(self) -> None: # logging.debug('Menu Back Called') # match self.lastMenu: # case Menus.MAIN: # logging.debug("Moving back to the main menu") # await self.main_menu() # case Menus.ARTISTALBUMS: # logging.debug("Moving back to Artist Albums") # await self.selectArtistAlbumView(self.selectedArtist.id) # case Menus.ARTISTS: # logging.debug("Moving backt to artists") # await self.selectArtistsView() # case _: # logging.debug(f"Last Menu unknown, {self.lastMenu}") def on_key(self, event: events.Key) -> None: logging.debug(f'Key Pressed {event.name}') # def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None: # logging.debug(f'{event.tab.id} selected') @on(Tabs.TabActivated) async def artists_tab(self, event: Tabs.TabActivated) -> None: if self.last_menu_type: await self.query_one(self.last_menu_type).remove() self.last_menu_type = None logging.debug(f'{event.tab.name} tab selected') id = event.tab.id match id: case "Artists": artistList: OptionList = await self._getArtistMenu() await self.mount(artistList) for artist in self.artists: artistList.add_option(Option(artist.name, artist.id)) self.last_menu_type = OptionList def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" self.theme = ( "textual-dark" if self.theme == "textual-light" else "textual-light" ) async def _getArtistMenu(self) -> OptionList: self.artists = self.conn.getArtists() return OptionList(id=Menus.ARTISTS.value) # @on(OptionList.OptionSelected, f'#{Menus.MAIN.value}') # async def selectMainMenu(self, event: OptionList.OptionSelected) -> None: # match event.option_id: # case "BrowseArtists": # logging.debug('View Artists Selected') # await self.selectArtistsView() # @on(OptionList.OptionSelected, f"#{Menus.ARTISTS.value}") # async def artistSelected(self, event: OptionList.OptionSelected) -> None: # logging.debug(f'Artist {event.option.prompt} Selected') # self.lastMenu = Menus.ARTISTS # for artist in self.artists: # if event.option_id == artist.id: # self.selectedArtist = artist # await self.selectArtistAlbumView(self.selectedArtist.id) # @on(DataTable.RowSelected, "#ArtistAlbums") # async def ArtistAlbumSelected(self, event: DataTable.RowSelected) -> None: # self.lastMenu = Menus.ARTISTALBUMS # row_key = event.row_key # row = event.data_table.get_row(row_key) # logging.debug(f'Album {row[0]} Selected') # await self.selectAlbumSongsView(row_key.value) async def selectAlbumSongsView(self, album_id: str) -> None: logging.debug("Setting up album songs DataTable") self.currentMenu = Menus.ALBUMSONGS songs = conn.getAlbumSongs(album_id) albumSongs = DataTable(id="AlbumSongs") await self.mount(albumSongs) albumSongs.cursor_type = 'row' albumSongs.add_columns("#", "Title", "Artist", "Duration", "Bitrate") for song in songs: albumSongs.add_row(song.trackNumber, song.title, song.artist, song.duration, song.bitRate, key=song.id) self.query_one(f"#{self.lastMenu.value}").remove() async def selectArtistAlbumView(self, artist_id: str) -> None: logging.debug("Setting up album DataTable") self.currentMenu = Menus.ARTISTALBUMS albumTable = DataTable(id="ArtistAlbums") await self.mount(albumTable) albumTable.cursor_type = 'row' albumTable.add_columns("Title", "Songs") self.albums = conn.getArtistAlbums(artist_id) for album in self.albums: albumTable.add_row(album.title, album.songCount, key=album.id) self.query_one(f"#{self.lastMenu.value}").remove() async def main_menu(self) -> None: await self.mount(OptionList( Option("Browse Artists", id="BrowseArtists"), Option("Browse Albums"), Option("Browse Songs"), Option("Browse Playlists"), id=Menus.MAIN.value )) self.query_one(f"#{self.currentMenu.value}").remove() async def selectArtistsView(self) -> None: artistList: OptionList = await self._getArtistMenu() self.currentMenu = Menus.ARTISTS await self.mount(artistList) for artist in self.artists: artistList.add_option(Option(artist.name, artist.id)) self.query_one(f"#{self.lastMenu.value}").remove() if __name__ == '__main__': app = TextualSubpyPlayer() app.conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net") app.run()