Compare commits

...

2 Commits
master ... tui

Author SHA1 Message Date
d716b5628a WIP: tui 2024-05-18 23:29:11 +02:00
76aaaa0a45 poetry: add textual, lock 2024-05-18 23:28:55 +02:00
4 changed files with 1031 additions and 432 deletions

View File

@ -0,0 +1,23 @@
/* https://textual.textualize.io/guide/design/#theme-reference */
Screen {
align: center top;
box-sizing: border-box;
}
Horizontal {
height: auto;
}
#header {
height: 1;
overflow: hidden;
}
#controls {
background: $background-lighten-2;
align: center middle;
}
#volumebar {
}
#seekbar {
}
DataTable#playlist {
width: 100%;
}

172
grzegorz_clients/tui.py Normal file
View File

@ -0,0 +1,172 @@
from . import api, utils
from pathlib import Path
from datetime import timedelta
import json
import os
import rich
import shutil
import subprocess
import sys
import typer
from textual.app import App, ComposeResult
from textual.containers import Vertical, Horizontal, VerticalScroll
from textual.widgets import Button, Static, ProgressBar, DataTable
from textual.timer import Timer
from textual import on
from textual.app import App, ComposeResult
import textual.containers as c
from textual.reactive import var
import textual.widgets as w
from textual.events import Mount
from textual.widgets import DirectoryTree, Footer, Header, Static
# export GRZEGORZ_DEFAULT_API_BASE="https://brzeczyszczykiewicz.pvv.ntnu.no/api"
# export GRZEGORZ_DEFAULT_API_BASE="https://georg.pvv.ntnu.no/api"
DEFAULT_API_BASE = os.environ.get("GRZEGORZ_DEFAULT_API_BASE", "https://brzeczyszczykiewicz.pvv.ntnu.no/api")
# url input
# prev - Button
# play/pause - Button
# next - Button
# loop - Switch
# shuffle - Button
# clear - Button
# position - ProgressBar
# playlist - datatable
# waveform - Sparkline
# LoadingIndicator
class GrzegorzApp(App):
CSS_PATH = "gzregorz.tcss"
#DEFAULT_CSS = ""
BINDINGS = [
("q", "quit", "Quit"),
]
refresh_timer: Timer
def compose(self) -> ComposeResult:
yield Static("Now playing: ", id="header")
yield Horizontal(
ProgressBar(id="seekbar", total=0, show_percentage=False, show_eta=False),
Static(" --:-- --:--", id="playtime"),
classes="center"
)
yield Horizontal(
ProgressBar(id="volumebar", total=100, show_eta=False),
classes="center"
)
yield Horizontal(
Button.error("Clear", id="btn-clear"),
Button("Prev", id="btn-prev"),
Button.success("Play", id="btn-play"),
Button("Next", variant="primary", id="btn-next"),
Button.warning("Shuffle", id="btn-shuffle"),
id="controls",
)
yield VerticalScroll(
DataTable(
id="playlist",
zebra_stripes=True,
cursor_type="row",
),
)
def on_mount(self) -> None:
api.set_endpoint("https://georg.pvv.ntnu.no/api")
self.refresh_timer = self.set_interval(1 / 1, self.do_update) # threaded
# self.log(api.get_playlist())
playlist: DataTable = self.query_one("#playlist")
playlist.add_columns("#", "Name", "length")
def do_update(self):
self.log("do_update")
# update status
vol = api.get_volume()
pos = api.get_playback_pos() or {"current": 0, "left": 0, "total": 0}
loop = api.get_playlist_looping()
play = api.is_playing()
self.log(f"""
{vol = }
{pos = }
{loop = }
{play = }
""")
# todo: sliders?
seek_var: ProgressBar = self.query_one("#seekbar")
seek_var.total = pos.get("total", 0)
seek_var.progress = pos.get("current", 0)
playtime: Static = self.query_one("#playtime")
playtime.update(f" --:-- - --:--" if not play else f" {timedelta(seconds=int(pos.get('current', 0)))} - {timedelta(seconds=int(pos.get('total', 0)))}")
vol_var: ProgressBar = self.query_one("#volumebar")
vol_var.progress = vol
btn_play: Button = self.query_one("#btn-play")
btn_play.label = "Pause" if play else "Play"
# btn_play.variant = "success" if play else "default"
# update playlist
playlist_data = api.get_playlist()
table = [
(
item.get("index", -1),
item.get("data", {}).get("title", None) or item["filename"],
utils.seconds_to_timestamp(item["data"]["duration"]) if "duration" in item.get("data", {}) else "--:--",
)
for item in playlist_data
]
current, = [item for item in playlist_data if item.get("current", False)][:1] or [None]
if current is not None:
self.query_one("#header").update("Now playing: " +
current.get("data", {}).get("title", None) or current["filename"]
)
playlist: DataTable = self.query_one("#playlist")
for r, row in enumerate(table[:playlist.row_count]):
for c, col in enumerate(row):
playlist.update_cell_at((r, c), col)
if playlist.row_count < len(table): # add more rows
playlist.add_rows(table[playlist.row_count:])
elif playlist.row_count > len(table): # remove extra rows
for i in range(playlist.row_count-1, len(table)-1, -1):
playlist.remove_row(playlist._row_locations.get_key(i))
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "btn-clear":
api.playlist_clear()
elif event.button.id == "btn-prev":
api.playlist_previous()
elif event.button.id == "btn-play":
api.set_playing(not api.is_playing())
elif event.button.id == "btn-next":
api.playlist_next()
elif event.button.id == "btn-shuffle":
api.playlist_shuffle()
@on(DataTable.RowSelected)
def on_data_table_row_selected(self, event: DataTable.RowSelected):
self.log("spismeg")
self.log(f"{event = }")
self.log(f"{event.cursor_row = }")
self.log(f"{event.row_key = }")
self.log("spismeg")
def main():
app = GrzegorzApp()
app.run()
if __name__ == "__main__":
main()

1259
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,19 +6,22 @@ authors = ["Peder Bergebakken Sundt <pbsds@hotmail.com>"]
license = "MIT" license = "MIT"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.7,<4.0" python = ">=3.8.1,<4.0"
remi = "1.0" remi = "1.0"
requests = ">=2.27.1,<3" requests = ">=2.27.1,<3"
rich = ">=13.3.5" rich = ">=13.3.5"
typer = ">=0.4.0" typer = ">=0.4.0"
urllib3 = ">=1.26.8,<3" urllib3 = ">=1.26.8,<3"
textual = {version = "^0.61.0", extras = ["tui"]}
[tool.poetry.dev-dependencies] [tool.poetry.group.dev.dependencies]
python-lsp-server = {extras = ["all"], version = "^1.5.0"} python-lsp-server = {extras = ["all"], version = "^1.11.0"}
textual-dev = "^1.5.1"
[tool.poetry.scripts] [tool.poetry.scripts]
grzegorz-webui = "grzegorz_clients.__main__:cli" grzegorz-webui = "grzegorz_clients.__main__:cli"
grzegorzctl = "grzegorz_clients.cli:cli" grzegorzctl = "grzegorz_clients.cli:cli"
grzegorztui = "grzegorz_clients.tui:main"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]