Decouple Timeline and TUI, use signals instead

pull/112/head
Ivan Habunek 2019-08-28 17:00:46 +02:00
rodzic 1a8c515922
commit 4deccee754
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: CDBD63C43A30BB95
3 zmienionych plików z 63 dodań i 57 usunięć

Wyświetl plik

@ -18,10 +18,11 @@ TODO/Ideas:
* Think about how to show media
* download media and use local image viewer?
* convert to ascii art?
* use signals to avoid tightly coupling components
* interaction with clipboard - how to copy a status to clipbard?
* Show **notifications**
* Show **threads**
* Status source
* shortcut to copy source
* syntax highlighting?
Questions:
* is it possible to make a span a urwid.Text selectable? e.g. for urls and hashtags

Wyświetl plik

@ -160,6 +160,19 @@ class TUI(urwid.Frame):
future = self.executor.submit(fn, *args, **kwargs)
future.add_done_callback(_done)
def connect_default_timeline_signals(self, timeline):
def _compose(*args):
self.show_compose()
def _source(timeline, status):
self.show_status_source(status)
urwid.connect_signal(timeline, "focus", self.refresh_footer)
urwid.connect_signal(timeline, "reblog", self.async_toggle_reblog)
urwid.connect_signal(timeline, "favourite", self.async_toggle_favourite)
urwid.connect_signal(timeline, "source", _source)
urwid.connect_signal(timeline, "compose", _compose)
def build_timeline(self, statuses):
def _close(*args):
raise urwid.ExitMainLoop()
@ -167,38 +180,36 @@ class TUI(urwid.Frame):
def _next(*args):
self.async_load_statuses(is_initial=False)
def _focus(timeline):
self.refresh_footer(timeline)
def _thread(timeline, status):
self.show_thread(status)
timeline = Timeline("home", self, statuses)
urwid.connect_signal(timeline, "focus", _focus)
timeline = Timeline("home", statuses)
self.connect_default_timeline_signals(timeline)
urwid.connect_signal(timeline, "next", _next)
urwid.connect_signal(timeline, "close", _close)
urwid.connect_signal(timeline, "thread", _thread)
return timeline
def show_thread(self, status):
def _close(*args):
"""When thread is closed, go back to the main timeline."""
self.body = self.timeline
self.body.refresh_status_details()
self.refresh_footer(self.timeline)
def _focus(timeline):
self.refresh_footer(timeline)
# This is pretty fast, so it's probably ok to block while context is
# loaded, can be made async later if needed
context = api.context(self.app, self.user, status.id)
ancestors = [Status(s, self.app.instance) for s in context["ancestors"]]
descendants = [Status(s, self.app.instance) for s in context["descendants"]]
statuses = ancestors + [status] + descendants
focus = len(ancestors)
statuses = ancestors + [status] + descendants
timeline = Timeline("thread", self, statuses, focus, is_thread=True)
urwid.connect_signal(timeline, "focus", _focus)
timeline = Timeline("thread", statuses, focus, is_thread=True)
self.connect_default_timeline_signals(timeline)
urwid.connect_signal(timeline, "close", _close)
self.body = timeline
@ -245,42 +256,25 @@ class TUI(urwid.Frame):
self.open_overlay(
widget=StatusSource(status),
title="Status source",
options={
"align": 'center',
"width": ('relative', 80),
"valign": 'middle',
"height": ('relative', 80),
},
)
def show_exception(self, exception):
self.open_overlay(
widget=ExceptionStackTrace(exception),
title="Unhandled Exception",
options={
"align": 'center',
"width": ('relative', 80),
"valign": 'middle',
"height": ('relative', 80),
},
)
def show_compose(self):
def _close(*args):
self.close_overlay()
def _post(timeline, content, warning, visibility):
self.post_status(content, warning, visibility)
composer = StatusComposer()
urwid.connect_signal(composer, "close",
lambda *args: self.close_overlay())
urwid.connect_signal(composer, "post",
lambda _, content, warning, visibility: self.post_status(content, warning, visibility))
self.open_overlay(
widget=composer,
title="Compose status",
options={
"align": 'center',
"width": ('relative', 80),
"valign": 'middle',
"height": ('relative', 80),
},
)
urwid.connect_signal(composer, "close", _close)
urwid.connect_signal(composer, "post", _post)
self.open_overlay(composer, title="Compose status")
def post_status(self, content, warning, visibility):
data = api.post_status(self.app, self.user, content,
@ -290,7 +284,7 @@ class TUI(urwid.Frame):
self.footer.set_message("Status posted {} \\o/".format(status.id))
self.close_overlay()
def async_toggle_favourite(self, status):
def async_toggle_favourite(self, timeline, status):
def _favourite():
logger.info("Favouriting {}".format(status))
api.favourite(self.app, self.user, status.id)
@ -303,14 +297,14 @@ class TUI(urwid.Frame):
# Create a new Status with flipped favourited flag
new_data = status.data
new_data["favourited"] = not status.favourited
self.timeline.update_status(Status(new_data, status.instance))
timeline.update_status(Status(new_data, status.instance))
self.run_in_thread(
_unfavourite if status.favourited else _favourite,
done_callback=_done
)
def async_toggle_reblog(self, status):
def async_toggle_reblog(self, timeline, status):
def _reblog():
logger.info("Reblogging {}".format(status))
api.reblog(self.app, self.user, status.id)
@ -323,7 +317,7 @@ class TUI(urwid.Frame):
# Create a new Status with flipped reblogged flag
new_data = status.data
new_data["reblogged"] = not status.reblogged
self.timeline.update_status(Status(new_data, status.instance))
timeline.update_status(Status(new_data, status.instance))
self.run_in_thread(
_unreblog if status.reblogged else _reblog,
@ -332,14 +326,22 @@ class TUI(urwid.Frame):
# --- Overlay handling -----------------------------------------------------
default_overlay_options = dict(
align="center", width=("relative", 80),
valign="middle", height=("relative", 80),
)
def open_overlay(self, widget, options={}, title=""):
top_widget = urwid.LineBox(widget, title=title)
bottom_widget = self.body
_options = self.default_overlay_options
_options.update(options)
self.overlay = urwid.Overlay(
top_widget,
bottom_widget,
**options
**_options
)
self.body = self.overlay

Wyświetl plik

@ -15,15 +15,18 @@ class Timeline(urwid.Columns):
Displays a list of statuses to the left, and status details on the right.
"""
signals = [
"focus",
"next",
"close",
"thread",
"close", # Close thread
"compose", # Compose a new toot
"favourite", # Favourite status
"focus", # Focus changed
"next", # Fetch more statuses
"reblog", # Reblog status
"source", # Show status source
"thread", # Show thread for status
]
def __init__(self, name, tui, statuses, focus=0, is_thread=False):
def __init__(self, name, statuses, focus=0, is_thread=False):
self.name = name
self.tui = tui
self.is_thread = is_thread
self.statuses = statuses
self.status_list = self.build_status_list(statuses, focus=focus)
@ -95,23 +98,23 @@ class Timeline(urwid.Columns):
if key in ("b", "B"):
status = self.get_focused_status()
self.tui.async_toggle_reblog(status)
self._emit("reblog", status)
return
if key in ('c', 'C'):
self.tui.show_compose()
if key in ("c", "C"):
self._emit("compose")
return
if key in ("f", "F"):
status = self.get_focused_status()
self.tui.async_toggle_favourite(status)
self._emit("favourite", status)
return
if key in ('q', 'Q'):
if key in ("q", "Q"):
self._emit("close")
return
if key in ('t', 'T'):
if key in ("t", "T"):
status = self.get_focused_status()
self._emit("thread", status)
return
@ -123,7 +126,7 @@ class Timeline(urwid.Columns):
if key in ("u", "U"):
status = self.get_focused_status()
self.tui.show_status_source(status)
self._emit("source", status)
return
return super().keypress(size, key)