diff --git a/toot/tui/app.py b/toot/tui/app.py index 2a60cb0..763ba2a 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -3,7 +3,7 @@ import urwid from concurrent.futures import ThreadPoolExecutor -from toot.api import home_timeline_generator +from toot import api from .constants import PALETTE from .entities import Status @@ -48,12 +48,14 @@ class Footer(urwid.Pile): def set_status(self, text): self.status.set_text(text) + def clear_status(self, text): + self.status.set_text(urwid.Text()) + def set_message(self, text): self.message.set_text(text) - def set_error(self, text): - # TODO: change to red - self.message.set_text(text) + def clear_message(self): + self.message.set_text(urwid.Text()) class TUI(urwid.Frame): @@ -80,7 +82,8 @@ class TUI(urwid.Frame): self.loop = None # set in `create` self.executor = ThreadPoolExecutor(max_workers=1) - self.timeline_generator = home_timeline_generator(app, user, limit=40) + # self.timeline_generator = api.home_timeline_generator(app, user, limit=40) + self.timeline_generator = api.public_timeline_generator(app.instance, local=False, limit=40) self.body = urwid.Filler(urwid.Text("Loading toots...", align="center")) self.header = Header(app, user) @@ -104,10 +107,23 @@ class TUI(urwid.Frame): def load_toots(self): data = next(self.timeline_generator) + with open("tmp/statuses2.json", "w") as f: + import json + json.dump(data, f, indent=4) + return [Status(s, self.app.instance) for s in data] def toots_loaded(self, future): self.body = Timeline(self, future.result()) + urwid.connect_signal(self.body, "status_focused", + lambda _, args: self.status_focused(*args)) + self.body.status_focused() # Draw first status + + def status_focused(self, status, index, count): + self.footer.set_status([ + ("footer_status_bold", "[home] "), status.id, + " - status ", str(index + 1), " of ", str(count), + ]) def unhandled_input(self, key): if key in ('q', 'Q'): diff --git a/toot/tui/constants.py b/toot/tui/constants.py index b9a7603..24a54ae 100644 --- a/toot/tui/constants.py +++ b/toot/tui/constants.py @@ -6,8 +6,12 @@ PALETTE = [ # Footer ('footer_status', 'white', 'dark blue'), + ('footer_status_bold', 'white, bold', 'dark blue'), ('footer_message', 'dark green', ''), + # Functional + ('link', ',italics', ''), + # by color name ('blue', 'light blue', ''), ('blue_bold', 'light blue, bold', ''), diff --git a/toot/tui/entities.py b/toot/tui/entities.py index a7def81..7a162f4 100644 --- a/toot/tui/entities.py +++ b/toot/tui/entities.py @@ -17,9 +17,13 @@ class Status: self.instance = instance self.id = self.data["id"] + self.display_name = self.data["account"]["display_name"] self.account = self.get_account() self.created_at = parse_datetime(data["created_at"]) + self.favourited = data.get("favourited", False) + self.reblogged = data.get("reblogged", False) + def get_account(self): acct = self.data['account']['acct'] return acct if "@" in acct else "{}@{}".format(acct, self.instance) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index f0eeba8..c4d9553 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -1,6 +1,8 @@ import logging import urwid +from toot.utils import format_content + from .widgets import SelectableText, SelectableColumns logger = logging.getLogger("toot") @@ -11,9 +13,12 @@ class Timeline(urwid.Columns): Displays a list of statuses to the left, and status details on the right. TODO: Switch to top/bottom for narrow views. + TODO: Cache rendered statuses? """ - - signals = ["status_focused"] + signals = [ + "status_focused", + "status_activated", + ] def __init__(self, tui, statuses): self.tui = tui @@ -21,10 +26,7 @@ class Timeline(urwid.Columns): self.instance = tui.app.instance self.status_list = self.build_status_list(statuses) - self.status_details = self.build_status_details(statuses[0], self.instance) - - # TODO: - # self.status_cache = {} + self.status_details = self.build_status_details(statuses[0]) super().__init__([ ("weight", 50, self.status_list), @@ -32,31 +34,13 @@ class Timeline(urwid.Columns): ], dividechars=1) def build_status_list(self, statuses): - items = [self.list_item(status) for status in statuses] + items = [self.build_list_item(status) for status in statuses] walker = urwid.SimpleFocusListWalker(items) urwid.connect_signal(walker, "modified", self.status_focused) return urwid.ListBox(walker) - def build_status_details(self, status, instance): - details = StatusDetails(status, instance) - return urwid.Filler(details, valign="top") - - def get_focused_status(self): - return self.statuses[self.status_list.body.focus] - - def status_activated(self, *args): - """Called when a status is clicked, or Enter is pressed.""" - # logger.info("status_activated " + str(args)) - - def status_focused(self): - """Called when the list focus switches to a new status""" - status = self.get_focused_status() - details = StatusDetails(status, self.instance) - self.status_details.set_body(details) - self._emit("status_focused", [status]) - - def list_item(self, status): - item = StatusListItem(status, self.instance) + def build_list_item(self, status): + item = StatusListItem(status) urwid.connect_signal(item, "click", self.status_activated) return urwid.AttrMap(item, None, focus_map={ "blue": "green_selected", @@ -65,20 +49,62 @@ class Timeline(urwid.Columns): None: "green_selected", }) + def build_status_details(self, status): + details = StatusDetails(status) + return urwid.Filler(details, valign="top") + + def get_focused_status(self): + return self.statuses[self.status_list.body.focus] + + def status_activated(self, *args): + """Called when a status is clicked, or Enter is pressed.""" + self._emit("status_activated", [status]) + + def status_focused(self): + """Called when the list focus switches to a new status""" + status = self.get_focused_status() + details = StatusDetails(status) + self.status_details.set_body(details) + + index = self.status_list.body.focus + count = len(self.statuses) + self._emit("status_focused", [status, index, count]) + class StatusDetails(urwid.Pile): - def __init__(self, status, instance): - return super().__init__([ - urwid.Text(status.id) - ]) + def __init__(self, status): + widget_list = list(self.content_generator(status)) + return super().__init__(widget_list) + def content_generator(self, status): + if status.display_name: + yield urwid.Text(("green", status.display_name)) + yield urwid.Text(("yellow", status.account)) + yield urwid.Divider() + + for line in format_content(status.data["content"]): + yield urwid.Text(line) + + if status.data["card"]: + yield urwid.Divider() + yield self.build_card(status.data["card"]) + + def card_generator(self, card): + yield urwid.Text(("green", card["title"].strip())) + if card["author_name"]: + yield urwid.Text(["by ", ("yellow", card["author_name"].strip())]) + yield urwid.Text(("link", card["url"])) + + def build_card(self, card): + contents = list(self.card_generator(card)) + return urwid.LineBox(urwid.Pile(contents)) class StatusListItem(SelectableColumns): - def __init__(self, status, instance): + def __init__(self, status): created_at = status.created_at.strftime("%Y-%m-%d %H:%M") - favourited = ("yellow", "★") if status.data["favourited"] else " " - reblogged = ("yellow", "⤶") if status.data["reblogged"] else " " + favourited = ("yellow", "★") if status.favourited else " " + reblogged = ("yellow", "⤶") if status.reblogged else " " return super().__init__([ ("pack", SelectableText(("blue", created_at), wrap="clip")), @@ -86,5 +112,6 @@ class StatusListItem(SelectableColumns): urwid.Text(("green", status.account), wrap="clip"), ("pack", urwid.Text(" ")), ("pack", urwid.Text(favourited)), + ("pack", urwid.Text(" ")), ("pack", urwid.Text(reblogged)), ])