mirror of
https://github.com/ihabunek/toot.git
synced 2025-06-30 22:18:36 -04:00
Decouple Timeline and TUI, use signals instead
This commit is contained in:
parent
1a8c515922
commit
4deccee754
@ -18,10 +18,11 @@ TODO/Ideas:
|
|||||||
* Think about how to show media
|
* Think about how to show media
|
||||||
* download media and use local image viewer?
|
* download media and use local image viewer?
|
||||||
* convert to ascii art?
|
* convert to ascii art?
|
||||||
* use signals to avoid tightly coupling components
|
|
||||||
* interaction with clipboard - how to copy a status to clipbard?
|
* interaction with clipboard - how to copy a status to clipbard?
|
||||||
* Show **notifications**
|
* Show **notifications**
|
||||||
* Show **threads**
|
* Status source
|
||||||
|
* shortcut to copy source
|
||||||
|
* syntax highlighting?
|
||||||
|
|
||||||
Questions:
|
Questions:
|
||||||
* is it possible to make a span a urwid.Text selectable? e.g. for urls and hashtags
|
* is it possible to make a span a urwid.Text selectable? e.g. for urls and hashtags
|
||||||
|
@ -160,6 +160,19 @@ class TUI(urwid.Frame):
|
|||||||
future = self.executor.submit(fn, *args, **kwargs)
|
future = self.executor.submit(fn, *args, **kwargs)
|
||||||
future.add_done_callback(_done)
|
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 build_timeline(self, statuses):
|
||||||
def _close(*args):
|
def _close(*args):
|
||||||
raise urwid.ExitMainLoop()
|
raise urwid.ExitMainLoop()
|
||||||
@ -167,38 +180,36 @@ class TUI(urwid.Frame):
|
|||||||
def _next(*args):
|
def _next(*args):
|
||||||
self.async_load_statuses(is_initial=False)
|
self.async_load_statuses(is_initial=False)
|
||||||
|
|
||||||
def _focus(timeline):
|
|
||||||
self.refresh_footer(timeline)
|
|
||||||
|
|
||||||
def _thread(timeline, status):
|
def _thread(timeline, status):
|
||||||
self.show_thread(status)
|
self.show_thread(status)
|
||||||
|
|
||||||
timeline = Timeline("home", self, statuses)
|
timeline = Timeline("home", statuses)
|
||||||
urwid.connect_signal(timeline, "focus", _focus)
|
|
||||||
|
self.connect_default_timeline_signals(timeline)
|
||||||
urwid.connect_signal(timeline, "next", _next)
|
urwid.connect_signal(timeline, "next", _next)
|
||||||
urwid.connect_signal(timeline, "close", _close)
|
urwid.connect_signal(timeline, "close", _close)
|
||||||
urwid.connect_signal(timeline, "thread", _thread)
|
urwid.connect_signal(timeline, "thread", _thread)
|
||||||
|
|
||||||
return timeline
|
return timeline
|
||||||
|
|
||||||
def show_thread(self, status):
|
def show_thread(self, status):
|
||||||
def _close(*args):
|
def _close(*args):
|
||||||
|
"""When thread is closed, go back to the main timeline."""
|
||||||
self.body = self.timeline
|
self.body = self.timeline
|
||||||
self.body.refresh_status_details()
|
self.body.refresh_status_details()
|
||||||
self.refresh_footer(self.timeline)
|
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
|
# This is pretty fast, so it's probably ok to block while context is
|
||||||
# loaded, can be made async later if needed
|
# loaded, can be made async later if needed
|
||||||
context = api.context(self.app, self.user, status.id)
|
context = api.context(self.app, self.user, status.id)
|
||||||
ancestors = [Status(s, self.app.instance) for s in context["ancestors"]]
|
ancestors = [Status(s, self.app.instance) for s in context["ancestors"]]
|
||||||
descendants = [Status(s, self.app.instance) for s in context["descendants"]]
|
descendants = [Status(s, self.app.instance) for s in context["descendants"]]
|
||||||
|
statuses = ancestors + [status] + descendants
|
||||||
focus = len(ancestors)
|
focus = len(ancestors)
|
||||||
|
|
||||||
statuses = ancestors + [status] + descendants
|
timeline = Timeline("thread", statuses, focus, is_thread=True)
|
||||||
timeline = Timeline("thread", self, statuses, focus, is_thread=True)
|
|
||||||
urwid.connect_signal(timeline, "focus", _focus)
|
self.connect_default_timeline_signals(timeline)
|
||||||
urwid.connect_signal(timeline, "close", _close)
|
urwid.connect_signal(timeline, "close", _close)
|
||||||
|
|
||||||
self.body = timeline
|
self.body = timeline
|
||||||
@ -245,42 +256,25 @@ class TUI(urwid.Frame):
|
|||||||
self.open_overlay(
|
self.open_overlay(
|
||||||
widget=StatusSource(status),
|
widget=StatusSource(status),
|
||||||
title="Status source",
|
title="Status source",
|
||||||
options={
|
|
||||||
"align": 'center',
|
|
||||||
"width": ('relative', 80),
|
|
||||||
"valign": 'middle',
|
|
||||||
"height": ('relative', 80),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_exception(self, exception):
|
def show_exception(self, exception):
|
||||||
self.open_overlay(
|
self.open_overlay(
|
||||||
widget=ExceptionStackTrace(exception),
|
widget=ExceptionStackTrace(exception),
|
||||||
title="Unhandled Exception",
|
title="Unhandled Exception",
|
||||||
options={
|
|
||||||
"align": 'center',
|
|
||||||
"width": ('relative', 80),
|
|
||||||
"valign": 'middle',
|
|
||||||
"height": ('relative', 80),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_compose(self):
|
def show_compose(self):
|
||||||
|
def _close(*args):
|
||||||
|
self.close_overlay()
|
||||||
|
|
||||||
|
def _post(timeline, content, warning, visibility):
|
||||||
|
self.post_status(content, warning, visibility)
|
||||||
|
|
||||||
composer = StatusComposer()
|
composer = StatusComposer()
|
||||||
urwid.connect_signal(composer, "close",
|
urwid.connect_signal(composer, "close", _close)
|
||||||
lambda *args: self.close_overlay())
|
urwid.connect_signal(composer, "post", _post)
|
||||||
urwid.connect_signal(composer, "post",
|
self.open_overlay(composer, title="Compose status")
|
||||||
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),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def post_status(self, content, warning, visibility):
|
def post_status(self, content, warning, visibility):
|
||||||
data = api.post_status(self.app, self.user, content,
|
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.footer.set_message("Status posted {} \\o/".format(status.id))
|
||||||
self.close_overlay()
|
self.close_overlay()
|
||||||
|
|
||||||
def async_toggle_favourite(self, status):
|
def async_toggle_favourite(self, timeline, status):
|
||||||
def _favourite():
|
def _favourite():
|
||||||
logger.info("Favouriting {}".format(status))
|
logger.info("Favouriting {}".format(status))
|
||||||
api.favourite(self.app, self.user, status.id)
|
api.favourite(self.app, self.user, status.id)
|
||||||
@ -303,14 +297,14 @@ class TUI(urwid.Frame):
|
|||||||
# Create a new Status with flipped favourited flag
|
# Create a new Status with flipped favourited flag
|
||||||
new_data = status.data
|
new_data = status.data
|
||||||
new_data["favourited"] = not status.favourited
|
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(
|
self.run_in_thread(
|
||||||
_unfavourite if status.favourited else _favourite,
|
_unfavourite if status.favourited else _favourite,
|
||||||
done_callback=_done
|
done_callback=_done
|
||||||
)
|
)
|
||||||
|
|
||||||
def async_toggle_reblog(self, status):
|
def async_toggle_reblog(self, timeline, status):
|
||||||
def _reblog():
|
def _reblog():
|
||||||
logger.info("Reblogging {}".format(status))
|
logger.info("Reblogging {}".format(status))
|
||||||
api.reblog(self.app, self.user, status.id)
|
api.reblog(self.app, self.user, status.id)
|
||||||
@ -323,7 +317,7 @@ class TUI(urwid.Frame):
|
|||||||
# Create a new Status with flipped reblogged flag
|
# Create a new Status with flipped reblogged flag
|
||||||
new_data = status.data
|
new_data = status.data
|
||||||
new_data["reblogged"] = not status.reblogged
|
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(
|
self.run_in_thread(
|
||||||
_unreblog if status.reblogged else _reblog,
|
_unreblog if status.reblogged else _reblog,
|
||||||
@ -332,14 +326,22 @@ class TUI(urwid.Frame):
|
|||||||
|
|
||||||
# --- Overlay handling -----------------------------------------------------
|
# --- Overlay handling -----------------------------------------------------
|
||||||
|
|
||||||
|
default_overlay_options = dict(
|
||||||
|
align="center", width=("relative", 80),
|
||||||
|
valign="middle", height=("relative", 80),
|
||||||
|
)
|
||||||
|
|
||||||
def open_overlay(self, widget, options={}, title=""):
|
def open_overlay(self, widget, options={}, title=""):
|
||||||
top_widget = urwid.LineBox(widget, title=title)
|
top_widget = urwid.LineBox(widget, title=title)
|
||||||
bottom_widget = self.body
|
bottom_widget = self.body
|
||||||
|
|
||||||
|
_options = self.default_overlay_options
|
||||||
|
_options.update(options)
|
||||||
|
|
||||||
self.overlay = urwid.Overlay(
|
self.overlay = urwid.Overlay(
|
||||||
top_widget,
|
top_widget,
|
||||||
bottom_widget,
|
bottom_widget,
|
||||||
**options
|
**_options
|
||||||
)
|
)
|
||||||
self.body = self.overlay
|
self.body = self.overlay
|
||||||
|
|
||||||
|
@ -15,15 +15,18 @@ class Timeline(urwid.Columns):
|
|||||||
Displays a list of statuses to the left, and status details on the right.
|
Displays a list of statuses to the left, and status details on the right.
|
||||||
"""
|
"""
|
||||||
signals = [
|
signals = [
|
||||||
"focus",
|
"close", # Close thread
|
||||||
"next",
|
"compose", # Compose a new toot
|
||||||
"close",
|
"favourite", # Favourite status
|
||||||
"thread",
|
"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.name = name
|
||||||
self.tui = tui
|
|
||||||
self.is_thread = is_thread
|
self.is_thread = is_thread
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
self.status_list = self.build_status_list(statuses, focus=focus)
|
self.status_list = self.build_status_list(statuses, focus=focus)
|
||||||
@ -95,23 +98,23 @@ class Timeline(urwid.Columns):
|
|||||||
|
|
||||||
if key in ("b", "B"):
|
if key in ("b", "B"):
|
||||||
status = self.get_focused_status()
|
status = self.get_focused_status()
|
||||||
self.tui.async_toggle_reblog(status)
|
self._emit("reblog", status)
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in ('c', 'C'):
|
if key in ("c", "C"):
|
||||||
self.tui.show_compose()
|
self._emit("compose")
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in ("f", "F"):
|
if key in ("f", "F"):
|
||||||
status = self.get_focused_status()
|
status = self.get_focused_status()
|
||||||
self.tui.async_toggle_favourite(status)
|
self._emit("favourite", status)
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in ('q', 'Q'):
|
if key in ("q", "Q"):
|
||||||
self._emit("close")
|
self._emit("close")
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in ('t', 'T'):
|
if key in ("t", "T"):
|
||||||
status = self.get_focused_status()
|
status = self.get_focused_status()
|
||||||
self._emit("thread", status)
|
self._emit("thread", status)
|
||||||
return
|
return
|
||||||
@ -123,7 +126,7 @@ class Timeline(urwid.Columns):
|
|||||||
|
|
||||||
if key in ("u", "U"):
|
if key in ("u", "U"):
|
||||||
status = self.get_focused_status()
|
status = self.get_focused_status()
|
||||||
self.tui.show_status_source(status)
|
self._emit("source", status)
|
||||||
return
|
return
|
||||||
|
|
||||||
return super().keypress(size, key)
|
return super().keypress(size, key)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user