Toot-Mastodon-CLI-TUI-clien.../toot/tui/timeline.py

215 lines
7.3 KiB
Python
Raw Normal View History

import logging
import urwid
2019-08-24 13:13:22 +02:00
import webbrowser
2019-08-24 12:53:55 +02:00
from toot.utils import format_content
2019-08-24 14:14:46 +02:00
from .utils import highlight_hashtags
from .widgets import SelectableText, SelectableColumns
logger = logging.getLogger("toot")
class Timeline(urwid.Columns):
"""
Displays a list of statuses to the left, and status details on the right.
"""
2019-08-24 12:53:55 +02:00
signals = [
"status_focused",
"status_activated",
2019-08-24 13:13:22 +02:00
"next",
2019-08-24 12:53:55 +02:00
]
def __init__(self, tui, statuses):
self.tui = tui
self.statuses = statuses
self.status_list = self.build_status_list(statuses)
2019-08-25 17:58:46 +02:00
self.status_details = StatusDetails(statuses[0])
# Maps status ID to its index in the list
self.status_index_map = {
status.id: n for n, status in enumerate(statuses)
}
super().__init__([
("weight", 40, self.status_list),
("weight", 60, self.status_details),
], dividechars=1)
def build_status_list(self, statuses):
2019-08-24 12:53:55 +02:00
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)
2019-08-24 12:53:55 +02:00
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",
"green": "green_selected",
"yellow": "green_selected",
None: "green_selected",
})
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."""
2019-08-24 13:43:41 +02:00
status = self.get_focused_status()
2019-08-24 12:53:55 +02:00
self._emit("status_activated", [status])
def status_focused(self):
"""Called when the list focus switches to a new status"""
status = self.get_focused_status()
2019-08-27 10:02:13 +02:00
self.draw_status_details(status)
2019-08-24 12:53:55 +02:00
index = self.status_list.body.focus
count = len(self.statuses)
self._emit("status_focused", [status, index, count])
2019-08-27 10:02:13 +02:00
def draw_status_details(self, status):
self.status_details = StatusDetails(status)
self.contents[1] = self.status_details, ("weight", 50, False)
2019-08-24 13:13:22 +02:00
def keypress(self, size, key):
# If down is pressed on last status in list emit a signal to load more.
# TODO: Consider pre-loading statuses earlier
command = self._command_map[key]
if command in [urwid.CURSOR_DOWN, urwid.CURSOR_PAGE_DOWN]:
index = self.status_list.body.focus + 1
count = len(self.statuses)
if index >= count:
self._emit("next")
if key in ("b", "B"):
status = self.get_focused_status()
self.tui.async_toggle_reblog(status)
return
2019-08-27 10:02:13 +02:00
if key in ('c', 'C'):
self.tui.show_compose()
return
if key in ("f", "F"):
status = self.get_focused_status()
self.tui.async_toggle_favourite(status)
return
2019-08-24 13:13:22 +02:00
if key in ("v", "V"):
status = self.get_focused_status()
webbrowser.open(status.data["url"])
return
2019-08-25 14:30:57 +02:00
if key in ("u", "U"):
status = self.get_focused_status()
self.tui.show_status_source(status)
return
2019-08-24 13:13:22 +02:00
return super().keypress(size, key)
def add_status(self, status):
self.statuses.append(status)
self.status_index_map[status.id] = len(self.statuses) - 1
self.status_list.body.append(self.build_list_item(status))
2019-08-27 10:02:13 +02:00
def prepend_status(self, status):
self.statuses.insert(0, status)
# Need to rebuild the map, there has to be a better way
self.status_index_map = {
status.id: n for n, status in enumerate(self.statuses)
}
self.status_list.body.insert(0, self.build_list_item(status))
if self.status_list.body.focus == 0:
self.draw_status_details(status)
2019-08-24 13:13:22 +02:00
def add_statuses(self, statuses):
for status in statuses:
self.add_status(status)
def update_status(self, status):
"""Overwrite status in list with the new instance and redraw."""
index = self.status_index_map[status.id]
assert self.statuses[index].id == status.id
# Update internal status list
self.statuses[index] = status
# Redraw list item
self.status_list.body[index] = self.build_list_item(status)
2019-08-24 13:13:22 +02:00
# Redraw status details if status is focused
if index == self.status_list.body.focus:
2019-08-27 10:02:13 +02:00
self.draw_status_details(status)
class StatusDetails(urwid.Pile):
2019-08-24 12:53:55 +02:00
def __init__(self, status):
widget_list = list(self.content_generator(status))
return super().__init__(widget_list)
def content_generator(self, status):
2019-08-24 13:43:41 +02:00
if status.data["reblog"]:
2019-08-25 10:00:48 +02:00
boosted_by = status.data["account"]["display_name"]
2019-08-25 17:58:46 +02:00
yield ("pack", urwid.Text(("gray", "{} boosted".format(boosted_by))))
yield ("pack", urwid.AttrMap(urwid.Divider("-"), "gray"))
2019-08-24 13:43:41 +02:00
if status.author.display_name:
2019-08-25 17:58:46 +02:00
yield ("pack", urwid.Text(("green", status.author.display_name)))
2019-08-25 10:00:48 +02:00
2019-08-25 17:58:46 +02:00
yield ("pack", urwid.Text(("yellow", status.author.account)))
yield ("pack", urwid.Divider())
2019-08-24 12:53:55 +02:00
for line in format_content(status.data["content"]):
2019-08-25 17:58:46 +02:00
yield ("pack", urwid.Text(highlight_hashtags(line)))
2019-08-24 12:53:55 +02:00
if status.data["card"]:
2019-08-25 17:58:46 +02:00
yield ("pack", urwid.Divider())
yield ("pack", self.build_card(status.data["card"]))
# Push things to bottom
yield ("weight", 1, urwid.SolidFill(" "))
yield ("pack", urwid.Text([
("cyan_bold", "B"), ("cyan", "oost"), " | ",
("cyan_bold", "F"), ("cyan", "avourite"), " | ",
2019-08-27 10:02:13 +02:00
("cyan_bold", "V"), ("cyan", "iew"), " | ",
("cyan", "So"), ("cyan_bold", "u"), ("cyan", "rce"), " | ",
2019-08-25 17:58:46 +02:00
("cyan_bold", "H"), ("cyan", "elp"), " ",
]))
2019-08-24 12:53:55 +02:00
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())])
2019-08-24 14:14:46 +02:00
yield urwid.Text("")
if card["description"]:
yield urwid.Text(card["description"].strip())
yield urwid.Text("")
2019-08-24 12:53:55 +02:00
yield urwid.Text(("link", card["url"]))
def build_card(self, card):
contents = list(self.card_generator(card))
2019-08-25 17:58:46 +02:00
card = urwid.Pile(contents)
card = urwid.Padding(card, left=1, right=1)
return urwid.LineBox(card)
class StatusListItem(SelectableColumns):
2019-08-24 12:53:55 +02:00
def __init__(self, status):
created_at = status.created_at.strftime("%Y-%m-%d %H:%M")
2019-08-24 12:53:55 +02:00
favourited = ("yellow", "") if status.favourited else " "
reblogged = ("yellow", "") if status.reblogged else " "
return super().__init__([
("pack", SelectableText(("blue", created_at), wrap="clip")),
("pack", urwid.Text(" ")),
urwid.Text(("green", status.account), wrap="clip"),
("pack", urwid.Text(" ")),
("pack", urwid.Text(favourited)),
2019-08-24 12:53:55 +02:00
("pack", urwid.Text(" ")),
("pack", urwid.Text(reblogged)),
])