From 95938d123a14430721795b901cd96bdc4e8378df Mon Sep 17 00:00:00 2001 From: Romanin <60302782+romanin-rf@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:49:55 +0200 Subject: [PATCH 1/2] Update 0.8.2 - Added language merge (for translating plugins) - Fixed in translation files (`Log Menu Enable` -> `Logging' ) - Changed `object.css` (classes will no longer be used to specify standard properties) - Improved widget `FillLabel` --- pyproject.toml | 2 +- seaplayer/css/objects.tcss | 47 +++++++++-------- seaplayer/css/seaplayer.tcss | 9 ++++ seaplayer/langs/en-eng.properties | 4 +- seaplayer/langs/ru-rus.properties | 4 +- seaplayer/langs/uk-ukr.properties | 4 +- seaplayer/languages.py | 83 +++++++++++++++++++++++++++---- seaplayer/objects/Configurate.py | 8 ++- seaplayer/objects/FullLabel.py | 14 ------ seaplayer/objects/Image.py | 6 +-- seaplayer/objects/Labels.py | 24 +++++++++ seaplayer/objects/MusicList.py | 37 ++++++++------ seaplayer/objects/ProgressBar.py | 4 +- seaplayer/objects/Rheostat.py | 3 +- seaplayer/objects/__init__.py | 1 + seaplayer/screens/Configurate.py | 2 +- seaplayer/seaplayer.py | 1 + seaplayer/types/Convert.py | 11 ++-- seaplayer/types/MusicList.py | 4 ++ seaplayer/units.py | 2 +- 20 files changed, 181 insertions(+), 89 deletions(-) delete mode 100644 seaplayer/objects/FullLabel.py create mode 100644 seaplayer/objects/Labels.py diff --git a/pyproject.toml b/pyproject.toml index 37a868f..2eb6235 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SeaPlayer" -version = "0.8.1" +version = "0.8.2" description = "SeaPlayer is a player that works in the terminal." repository = "https://github.com/romanin-rf/SeaPlayer" authors = ["Romanin "] diff --git a/seaplayer/css/objects.tcss b/seaplayer/css/objects.tcss index 07bbb30..b5537b2 100644 --- a/seaplayer/css/objects.tcss +++ b/seaplayer/css/objects.tcss @@ -1,43 +1,42 @@ /* ! Music List */ -.music-list-view { +MusicListView { height: 1fr; } -.music-list-view-item { +MusicListViewItem { height: 4; } -.music-list-view-item-title-label { +MusicListViewItem .title-label { height: 1; color: #cacaca; } -.music-list-view-item-subtitle-label { +MusicListViewItem .subtitle-label { height: 1; color: #a9a9a9; } -.music-list-screen-add-box { - height: 3; -} - -.music-list-screen-add-input { - width: 1fr; -} - /* ! IndeterminateProgress */ -.indeterminate-progress-bar { height: 1; } +IndeterminateProgress { height: 1; } /* ! Rheostat */ -.rheostat-bar { height: 1; } -.rheostat { height: 4; } -.rheostat-horizontal { +RheostatBar { height: 1; } +Rheostat { height: 4; } +Rheostat Horizontal { align-horizontal: center; width: 1fr; } /* ! Image Label */ -.image-label { +StandartImageLabel { + height: 1fr; + width: 1fr; + align: center middle; + text-align: center; +} + +AsyncImageLabel { height: 1fr; width: 1fr; align: center middle; @@ -45,13 +44,13 @@ } /* ! Configurate List */ -.configurate-list-view { +ConfigurateList { border: solid cadetblue; height: 1fr; width: 1fr; } -.configurate-list-view-item { +ConfigurateListItem { border: solid dodgerblue; background: #0000; border-title-color: #aaaaaa; @@ -59,9 +58,6 @@ height: auto; } -/* ! Any Elements CSS */ -.pass-one-width { width: 1; } - /* ! Log Menu */ LogMenu { background: $surface; @@ -114,7 +110,10 @@ PopUp { } /* ! FullLabel */ -.full-label { +FillLabel { height: 1fr; width: 1fr; -} \ No newline at end of file +} + +/* ! Any Elements CSS */ +.pass-one-width { width: 1; } \ No newline at end of file diff --git a/seaplayer/css/seaplayer.tcss b/seaplayer/css/seaplayer.tcss index 7f24e05..035f849 100644 --- a/seaplayer/css/seaplayer.tcss +++ b/seaplayer/css/seaplayer.tcss @@ -38,3 +38,12 @@ align: center middle; text-align: center; } + +/* Playlist */ +.music-list-screen-add-box { + height: 3; +} + +.music-list-screen-add-input { + width: 1fr; +} \ No newline at end of file diff --git a/seaplayer/langs/en-eng.properties b/seaplayer/langs/en-eng.properties index 9ac463c..839369d 100644 --- a/seaplayer/langs/en-eng.properties +++ b/seaplayer/langs/en-eng.properties @@ -71,8 +71,8 @@ configurate.playlist="Playlist" configurate.playlist.recursive_search="Recursive Search" configurate.playlist.recursive_search.desc="Recursive file search." configurate.debug="Debug" -configurate.debug.log_menu_enable="Log Menu Enable" -configurate.debug.log_menu_enable.desc="Menu with logs for the current session." +configurate.debug.log_menu_enable="Logging" +configurate.debug.log_menu_enable.desc="Enabling/disabling logging." configurate.keys="Keys" configurate.keys.quit="Quit" configurate.keys.quit.desc="Сlose the app." diff --git a/seaplayer/langs/ru-rus.properties b/seaplayer/langs/ru-rus.properties index cd61a5f..dd8e5b6 100644 --- a/seaplayer/langs/ru-rus.properties +++ b/seaplayer/langs/ru-rus.properties @@ -71,8 +71,8 @@ configurate.playlist="Плейлист" configurate.playlist.recursive_search="Рекурсивный поиск" configurate.playlist.recursive_search.desc="Рекурсивный поиск файлов." configurate.debug="Дебаг" -configurate.debug.log_menu_enable="Меню логов" -configurate.debug.log_menu_enable.desc="Меню с логом для текущей сессии." +configurate.debug.log_menu_enable="Логирование" +configurate.debug.log_menu_enable.desc="Включение/выключение логирования." configurate.keys="Клавиши" configurate.keys.quit="Выход" configurate.keys.quit.desc="Закрыть приложение." diff --git a/seaplayer/langs/uk-ukr.properties b/seaplayer/langs/uk-ukr.properties index 2470cc1..a7d1dfd 100644 --- a/seaplayer/langs/uk-ukr.properties +++ b/seaplayer/langs/uk-ukr.properties @@ -70,8 +70,8 @@ configurate.playlist="Плейлист" configurate.playlist.recursive_search="Рекурсивний пошук" configurate.playlist.recursive_search.desc="Рекурсивний пошук файлів." configurate.debug="Дебаг" -configurate.debug.log_menu_enable="Меню логів" -configurate.debug.log_menu_enable.desc="Меню з логом для поточної сесії." +configurate.debug.log_menu_enable="Логування" +configurate.debug.log_menu_enable.desc="Включення/вимикання логування." configurate.keys="Клавіші" configurate.keys.quit="Вихід" configurate.keys.quit.desc="Закрити програму." diff --git a/seaplayer/languages.py b/seaplayer/languages.py index 93969ea..44ba42b 100644 --- a/seaplayer/languages.py +++ b/seaplayer/languages.py @@ -1,7 +1,7 @@ import os import glob import properties -from typing import Dict, Tuple, Optional +from typing import Dict, List, Tuple, Optional # > Local Imports from .functions import formater from .exceptions import LanguageNotExistError, LanguageNotLoadedError @@ -36,7 +36,8 @@ def __init__(self, language_filepath: str) -> None: # ? Magic Methods def __str__(self) -> str: - return f"{self.__class__.__name__}({formater(name=self.__name, title=self.__title, mark=self.__mark)})" + form = formater(name=self.__name, title=self.__title, mark=self.__mark, loaded=self.__loaded) + return f"{self.__class__.__name__}({form})" def __repr__(self) -> str: return self.__str__() @@ -85,17 +86,18 @@ def get(self, key: str, default: Optional[str]=None) -> Optional[str]: # ! Main Class class LanguageLoader: # ? Main Methods - def __search_langs(self, dlm: str, mlm: str) -> Tuple[Language, Language]: + def __search_langs(self, dlm: str, mlm: str) -> Tuple[Language, Optional[Language]]: dl, ml = None, None for lang in self.langs: if lang.mark == dlm: dl = lang if lang.mark == mlm: ml = lang - if (dl is None) or (ml is None): - raise LanguageNotExistError([dlm, mlm]) + if dl is None: + raise LanguageNotExistError([dl]) dl.load() - ml.load() + if ml is not None: + ml.load() return dl, ml # ? Initialization @@ -108,12 +110,73 @@ def __init__( self.__name = os.path.abspath(langs_dirpath) self.__mlm = main_lang_mark.lower() self.__dlm = default_lang_mark.lower() + self.__alangs: List[LanguageLoader] = [] # * Checking if not os.path.isdir(self.__name): raise FileNotFoundError - # * Searching Languages - self.langs = [Language(lp) for lp in glob.glob(os.path.join(self.__name, "??-???.properties"))] - self.dlang, self.mlang = self.__search_langs(self.__dlm, self.__mlm) + # * Searching and loading languages + self.__langs: List[Language] = [Language(lp) for lp in glob.glob(os.path.join(self.__name, "??-???.properties"))] + self.__dlang, self.__mlang = self.__search_langs(self.__dlm, self.__mlm) + # ? Magic Methods + def __str__(self) -> str: + form = formater( + name=self.__name, + default_lang_mark=self.__dlm, + main_lang_mark=self.__mlm, + langs=self.__langs, + alangs=self.__alangs + ) + return f"{self.__class__.__name__}({form})" + + def __repr__(self) -> str: + return self.__str__() + + # ? Propertys + @property + def name(self) -> str: + return self.__name + + @property + def main_lang_mark(self) -> str: + return self.__mlm + + @property + def default_lang_mark(self) -> str: + return self.__dlm + + @property + def langs(self) -> List[Language]: + return self.__langs + + @property + def default_lang(self) -> Language: + return self.__dlang + + @property + def main_lang(self) -> Optional[Language]: + return self.__mlang + + @property + def alangs(self): + """Additional languages, for example, translation of plugins. + + Returns: + List[LanguageLoader]: A language loader with its own list of languages, which is the last thing to get from. + """ + return self.__alangs + + # ? Public Methods def get(self, key: str) -> str: - return self.mlang.get(key, self.dlang.get(key, "")) + if self.__mlang is None: + data = self.__dlang.get(key) + data = self.__mlang.get(key, self.__dlang.get(key)) + if data is None: + for alang in self.__alangs: + if (data:=alang.get(key)) is not None: + break + return data if data is not None else "" + + def merge(self, ll) -> None: + assert isinstance(ll, LanguageLoader) + self.__alangs.append(ll) \ No newline at end of file diff --git a/seaplayer/objects/Configurate.py b/seaplayer/objects/Configurate.py index 202f86d..d4a888b 100644 --- a/seaplayer/objects/Configurate.py +++ b/seaplayer/objects/Configurate.py @@ -12,8 +12,7 @@ def __init__( width: Union[int, str]="1fr", height: Union[int, str]="1fr", **kwargs - ): - kwargs["classes"] = "configurate-list-view-item" + ) -> None: super().__init__(*children, **kwargs) self.border_title = title self.border_subtitle = desc @@ -26,6 +25,5 @@ async def updating(self, title: Optional[str]="", desc: Optional[str]="") -> Non # ! Main Class class ConfigurateList(ScrollableContainer): - def __init__(self, *children, **kwargs): - kwargs["classes"] = "configurate-list-view" - super().__init__(*children, **kwargs) \ No newline at end of file + def __init__(self, *children, **kwargs) -> None: + super().__init__(*children, **kwargs) diff --git a/seaplayer/objects/FullLabel.py b/seaplayer/objects/FullLabel.py deleted file mode 100644 index f7a6710..0000000 --- a/seaplayer/objects/FullLabel.py +++ /dev/null @@ -1,14 +0,0 @@ -from textual.widgets import Label - -# ! Main Class -class FullLabel(Label): - def _gen(self) -> str: - return self._chr * (self.size[0] * self.size[1]) - - def __init__(self, char: str="-") -> None: - super().__init__(classes="full-label") - self._chr = char - self.update(self._gen()) - - async def on_resize(self) -> None: - self.update(self._gen()) \ No newline at end of file diff --git a/seaplayer/objects/Image.py b/seaplayer/objects/Image.py index 63491cc..899367f 100644 --- a/seaplayer/objects/Image.py +++ b/seaplayer/objects/Image.py @@ -15,8 +15,8 @@ def __init__( image: Optional[Image.Image]=None, *, resample: Resampling=Resampling.NEAREST - ): - super().__init__("", classes="image-label") + ) -> None: + super().__init__("") self.image_resample = resample self.default_image: Image.Image = default_image self.image: Optional[Image.Image] = image @@ -51,7 +51,7 @@ def __init__( self.image: Optional[Image.Image] = image self.image_text: Union[str, AsyncPixels] = "" self.last_image_size: Optional[Tuple[int, int]] = None - super().__init__("", classes="image-label") + super().__init__("") async def on_resize(self) -> None: image, resample = (self.default_image, Resampling.NEAREST) if (self.image is None) else (self.image, self.image_resample) diff --git a/seaplayer/objects/Labels.py b/seaplayer/objects/Labels.py new file mode 100644 index 0000000..c8f7445 --- /dev/null +++ b/seaplayer/objects/Labels.py @@ -0,0 +1,24 @@ +from textual.widgets import Label +from rich.segment import Segments, Segment +from rich.style import Style +# > Typing +from typing import Optional + +# ! Main Class +class FillLabel(Label): + def _gen(self) -> Segments: + return Segments([Segment(self.__chr, self.__style) for i in range((self.size[0] * self.size[1]))]) + + def __init__( + self, + char: str="-", + style: Optional[Style]=None, + **kwargs + ) -> None: + super().__init__(**kwargs) + self.__chr = char[:1] + self.__style = style + self.update(self._gen()) + + async def on_resize(self) -> None: + self.update(self._gen()) \ No newline at end of file diff --git a/seaplayer/objects/MusicList.py b/seaplayer/objects/MusicList.py index 72141b7..37d50c5 100644 --- a/seaplayer/objects/MusicList.py +++ b/seaplayer/objects/MusicList.py @@ -2,7 +2,7 @@ # > Typing from typing import Optional # > Local Import's -from .FullLabel import FullLabel +from .Labels import FillLabel from ..types import MusicList from ..codeсbase import CodecBase from ..functions import get_sound_basename, aiter @@ -16,16 +16,16 @@ def __init__( second_subtitle: str="", sound_uuid: Optional[str]=None ) -> None: - super().__init__(classes="music-list-view-item") - self.title_label = Label(title, classes="music-list-view-item-title-label") - self.first_subtitle_label = Label(f" {first_subtitle}", classes="music-list-view-item-subtitle-label") - self.second_subtitle_label = Label(f" {second_subtitle}", classes="music-list-view-item-subtitle-label") + super().__init__() + self.title_label = Label(title, classes="title-label") + self.first_subtitle_label = Label(f" {first_subtitle}", classes="subtitle-label") + self.second_subtitle_label = Label(f" {second_subtitle}", classes="subtitle-label") self.sound_uuid = sound_uuid self.compose_add_child(self.title_label) self.compose_add_child(self.first_subtitle_label) self.compose_add_child(self.second_subtitle_label) - self.compose_add_child(FullLabel()) + self.compose_add_child(FillLabel()) async def update_labels( self, @@ -39,9 +39,8 @@ async def update_labels( # ! Main Class class MusicListView(ListView): - def __init__(self, **kwargs) -> None: - kwargs["classes"] = "music-list-view" - super().__init__(**kwargs) + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) self.music_list: MusicList = MusicList() def add_sound(self, sound: CodecBase) -> str: @@ -85,8 +84,12 @@ async def aio_add_sound(self, sound: CodecBase): ) return sound_uuid - def get_items_count(self) -> int: return len(self.children) - def exists_item_index(self, index: int) -> bool: return 0 >= index < self.get_items_count() + def get_items_count(self) -> int: + return len(self.children) + + def exists_item_index(self, index: int) -> bool: + return 0 >= index < self.get_items_count() + def get_item_index_from_sound_uuid(self, sound_uuid: str) -> Optional[int]: item: MusicListViewItem for idx, item in enumerate(self.children): @@ -94,10 +97,13 @@ def get_item_index_from_sound_uuid(self, sound_uuid: str) -> Optional[int]: return idx def get_item_from_index(self, index: int) -> Optional[MusicListViewItem]: - try: return self.children[index] + try: + return self.children[index] except: - try: return self.children[0] - except: pass + try: + return self.children[0] + except: + pass def get_next_sound_uuid(self, sound_uuid: str) -> Optional[str]: if (index:=self.get_item_index_from_sound_uuid(sound_uuid)) is not None: @@ -137,4 +143,5 @@ async def aio_select_list_item_from_sound_uuid(self, sound_uuid: str) -> None: self.children[await self.aio_get_item_index_from_sound_uuid(sound_uuid)] ) ) - except: pass \ No newline at end of file + except: + pass \ No newline at end of file diff --git a/seaplayer/objects/ProgressBar.py b/seaplayer/objects/ProgressBar.py index 11bc4a4..5b99847 100644 --- a/seaplayer/objects/ProgressBar.py +++ b/seaplayer/objects/ProgressBar.py @@ -5,14 +5,14 @@ # > Local Import's from ..functions import get_bar_status - +# ! Main Class class IndeterminateProgress(Static): def __init__( self, getfunc: Callable[[], Coroutine[Any, Any, Tuple[str, Optional[float], Optional[float]]]]=get_bar_status, fps: int=10 ) -> None: - super().__init__("", classes="indeterminate-progress-bar") + super().__init__() self._bar = Progress(BarColumn(), TextColumn("{task.description}")) self._task_id = self._bar.add_task("", total=None) self._fps = fps diff --git a/seaplayer/objects/Rheostat.py b/seaplayer/objects/Rheostat.py index 5f9aab9..b186a8f 100644 --- a/seaplayer/objects/Rheostat.py +++ b/seaplayer/objects/Rheostat.py @@ -116,8 +116,7 @@ def __init__( Horizontal( ClikableButton("-", self.__click_minus), self.label, - ClikableButton("+", self.__click_plus), - classes="rheostat-horizontal" + ClikableButton("+", self.__click_plus) ), classes="rheostat" ) \ No newline at end of file diff --git a/seaplayer/objects/__init__.py b/seaplayer/objects/__init__.py index 3e76ba8..1a80cdd 100644 --- a/seaplayer/objects/__init__.py +++ b/seaplayer/objects/__init__.py @@ -1,4 +1,5 @@ from .Log import LogMenu +from .Labels import FillLabel from .Input import InputField from .Notification import Nofy, CallNofy from .ProgressBar import IndeterminateProgress diff --git a/seaplayer/screens/Configurate.py b/seaplayer/screens/Configurate.py index 02ef322..2be9fbc 100644 --- a/seaplayer/screens/Configurate.py +++ b/seaplayer/screens/Configurate.py @@ -180,7 +180,7 @@ def create_configurator_literal( return ConfigurateListItem( DataRadioSet(on_changed_method, *buttons), title="[red]{"+group+"}[/red]: "+title, - desc=desc + f" [red]({self.ll.get('words.restart_required')})[/red]" if restart_required else "", + desc=desc+(f" [red]({self.ll.get('words.restart_required')})[/red]" if restart_required else ""), height=len(values)+4 ) diff --git a/seaplayer/seaplayer.py b/seaplayer/seaplayer.py index d4dede8..d8d4131 100644 --- a/seaplayer/seaplayer.py +++ b/seaplayer/seaplayer.py @@ -281,6 +281,7 @@ def compose(self) -> ComposeResult: self.info(f"CSS Dirpath : {repr(CSS_LOCALDIR)}") self.info(f"Assets Dirpath : {repr(ASSETS_DIRPATH)}") self.info(f"Codecs Kwargs : {repr(self.CODECS_KWARGS)}") + self.info(f"Language Loader : {repr(self.ll)}") # * Play Screen self.music_play_screen = Container(classes="screen-box") diff --git a/seaplayer/types/Convert.py b/seaplayer/types/Convert.py index f890fed..211d00b 100644 --- a/seaplayer/types/Convert.py +++ b/seaplayer/types/Convert.py @@ -7,6 +7,7 @@ NotBooleanError ) +# ! Main Class class Converter: def __init__(self, *args, **kwargs) -> None: self.args = args @@ -19,6 +20,11 @@ def conv(tp: type, value: str) -> Tuple[bool, Optional[Any]]: except: return False, None + def gen_conv(self, tp: type): + def conv_wrapper(value: str) -> Tuple[bool, Optional[Any]]: + return self.conv(tp, value) + return conv_wrapper + @staticmethod async def aio_conv(tp: type, value: str) -> Tuple[bool, Optional[Any]]: try: @@ -26,11 +32,6 @@ async def aio_conv(tp: type, value: str) -> Tuple[bool, Optional[Any]]: except: return False, None - def gen_conv(self, tp: type): - def conv_wrapper(value: str) -> Tuple[bool, Optional[Any]]: - return self.conv(tp, value) - return conv_wrapper - def gen_aio_conv(self, tp: type): async def aio_conv_wrapper(value: str) -> Tuple[bool, Optional[Any]]: return await self.aio_conv(tp, value) diff --git a/seaplayer/types/MusicList.py b/seaplayer/types/MusicList.py index 67d4207..2a91d4b 100644 --- a/seaplayer/types/MusicList.py +++ b/seaplayer/types/MusicList.py @@ -5,6 +5,7 @@ # ! Main Class class MusicList: + # ? Preload Methods @staticmethod def get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: return sound.__sha1__(buffer_size) @@ -13,12 +14,14 @@ def get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: async def aio_get_file_sha1(sound: CodecBase, buffer_size: int=65536) -> str: return await sound.__aio_sha1__(buffer_size) + # ? Initialization def __init__(self, **child_sounds: CodecBase) -> None: self.sounds: Dict[str, CodecBase] = {} for key in child_sounds: if isinstance(child_sounds[key], CodecBase): self.sounds[key] = child_sounds[key] + # ? Main Sync Methods def exists(self, sound_uuid: str) -> bool: return sound_uuid in self.sounds.keys() @@ -32,6 +35,7 @@ def add(self, sound: CodecBase) -> str: def exists_sha1(self, sound: CodecBase) -> bool: return self.get_file_sha1(sound) in self.sounds.keys() + # ? Main Async Methods async def aio_exists(self, sound_uuid: str): return sound_uuid in self.sounds.keys() diff --git a/seaplayer/units.py b/seaplayer/units.py index 2cf34a0..f304080 100644 --- a/seaplayer/units.py +++ b/seaplayer/units.py @@ -6,7 +6,7 @@ # ! Metadata __title__ = "SeaPlayer" -__version__ = "0.8.1" +__version__ = "0.8.2" __author__ = "Romanin" __email__ = "semina054@gmail.com" __url__ = "https://github.com/romanin-rf/SeaPlayer" From c2b7c0996c75b02d7daa609050c010c7db40c2d2 Mon Sep 17 00:00:00 2001 From: Romanin <60302782+romanin-rf@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:51:16 +0200 Subject: [PATCH 2/2] Update changelog.md --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 0a3aceb..6cb735c 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ | Version | Date | Tag | Changelog | | ------- | ---- | --- | --------- | +| [v0.8.2](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.2) | 08.12.2023 | **STABLE** | - Added language merge (for translating plugins)
- Fixed in translation files (`Log Menu Enable` -> `Logging' )
- Changed `object.css` (classes will no longer be used to specify standard properties)
- Improved widget `FillLabel` | | [v0.8.1](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.1) | 07.12.2023 | **STABLE** | - Revisioned of the LanguageLoader
- Added new language: `Українська` | | [v0.8.0](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.8.0) | 07.12.2023 | **STABLE** | - Added a new experimental `PopUp` widget (pop-up window)
- Added new exception type `Error`
- Added language loader system
- Added full translate: English, Русский
- Added `changelog.md`
- Added `secrets`
- Added widgets: `DataRadioButton`, `ClikableButton` and `Rheostat (experemental)`
- Fixed: `self.last_playback_status` not is int
- Changed CSS for `LogMenu`
- Updated `build.py`
- Renamed `*.css` files to `.tcss`
- Removing excess in `DataOptions` (from past updates)
- Deprecated method `seaplayer.functions.check_status`
- The first part of the attempts to make the `Configurate Screen` understandable to the average user | | [v0.7.5](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.5) | 02.12.2023 | **STABLE** | - Updated the `Rich Discord Status` plugin (v0.2.1)
- Updated the `PluginLoader` (v0.3.0)
- Changed the backlight in the logs
- Transferred some methods to `functions.py`
- Removed excess (remaining from other updates) | @@ -10,4 +11,4 @@ | [v0.7.3](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.3) | 01.12.2023 | **DEPRECATED** | - Added log on loading of `SeaPlayer`
- Updated README.md
- Updated dependencies
- Updated class `Log`
- Fixed the `NotAContainer` bug
- Fixed *CLI* operation
- Fixed the `pip install` startup error | | [v0.7.2](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.2) | 29.11.2023 | **DEPRECATED** | - Added small optimizations | | [v0.7.1](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.1.post1) | 28.11.2023 | **DEPRECATED** | - Optimization due to more transplanting to a newer version of `playsoundsimple` | -| [v0.7.0](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.0) | 28.11.2023 | **DEPRECATED** | - Added installation of modules from a file `requirements.txt` in the folder of any plugin, if there is one
- Added more intuitive logging | +| [v0.7.0](https://github.com/romanin-rf/SeaPlayer/releases/tag/v0.7.0) | 28.11.2023 | **DEPRECATED** | - Added installation of modules from a file `requirements.txt` in the folder of any plugin, if there is one
- Added more intuitive logging | \ No newline at end of file