Commit fa98a1a6 authored by Lysander Trischler's avatar Lysander Trischler

Highlight invalid input

parent c9c0f49c
......@@ -81,6 +81,16 @@ looks like:
The live preview on the bottom updates as you type to help you proofread
your twts before publishing them.
.. figure:: disabled.png
A rudimentary input validation prevents invalid twts to be published and
highlights the problematic text input field along with a notice in the
rendered preview.
.. figure:: enabled.png
When everything is fine, the "Publish Twt" button is enabled again.
Features
========
......@@ -330,8 +340,12 @@ Default configuration::
subject-focus = black; dark red
link-normal = light blue,underline; black
link-focus = light blue,underline; white
error-normal = white,bold; dark red
error-focus = dark red,bold; white
error-message-normal = white,bold; dark red
error-message-focus = dark red,bold; white
error-edit-normal = dark red,bold; white
error-edit-focus = white,bold; dark red
error-box-normal = dark red; black
error-box-focus = light red; black
publish-normal = white; black
publish-focus = black; white
cancel-normal = white; black
......
enabled.png

51.9 KB

......@@ -451,22 +451,34 @@ class TwtForm(urwid.ListBox, TwtFormatter):
text = authors + subject
else:
text = ""
self._text_edit = widgets.TextEdit(edit_text=text)
self._text_edit_map = widgets.CachingAttrMap(self._text_edit, None)
self._text_box_map = widgets.CachingAttrMap(urwid.LineBox(self._text_edit_map,
title="Reply" if original_twt else "New Twt",
title_align="left"), None)
self._created_at_edit = widgets.TextEdit(edit_text=datetime.datetime.now(datetime.timezone.utc)
.replace(second=0,
microsecond=0)
.astimezone()
.isoformat())
self._created_at_edit_map = widgets.CachingAttrMap(self._created_at_edit, None)
self._created_at_box_map = widgets.CachingAttrMap(urwid.LineBox(self._created_at_edit_map,
title="Created at",
title_align="left"), None)
publish = widgets.PushButton("Publish Twt", on_press=self.publish_twt)
self._publish = widgets.SelectableProxyAttrMap(publish, "publish-normal", "publish-focus", "publish-disabled", selectable=False)
cancel = widgets.PushButton("Cancel", on_press=lambda *_: frame.body.pop_widget())
preview_widgets, rows_calculating_delegate_widget = self._render_new_twt()
preview_list_walker = urwid.SimpleFocusListWalker(preview_widgets)
preview_box_adapter = widgets.DelegatingBoxAdapter(widgets.VimListBox(preview_list_walker),
rows_calculating_delegate_widget)
super().__init__(rendered_conversation + [
urwid.LineBox(self._text_edit, title="Reply" if original_twt else "New Twt", title_align="left"),
urwid.LineBox(self._created_at_edit, title="Created at", title_align="left"),
self._text_box_map,
self._created_at_box_map,
urwid.LineBox(widgets.Unselectable(preview_box_adapter),
title="Reply Preview" if original_twt else "New Twt Preview",
title_align="left"),
......@@ -501,20 +513,45 @@ class TwtForm(urwid.ListBox, TwtFormatter):
created_at_error = True
else:
created_at_error = False
text = self._text
# The Tweet constructor will fail for empty texts, so we need to guard
# against this.
new_twt = manager.enhance_twt(twtxt.models.Tweet(created_at=created_at,
text=self._text or " ",
text=text or " ",
source=manager.own_source))
preview_widgets = []
self.render_conversation(manager, new_twt, preview_widgets, [],
level=0 if self._original_twt is None else 1)
rows_calculating_delegate_widget = preview_widgets[-1].original_widget
if created_at_error:
created_at_text = rows_calculating_delegate_widget[0]
created_at_text.set_text(("error-normal", "Invalid timestamp"))
self._publish.set_selectable(not created_at_error and self._text)
created_at_text.set_text(("error-message-normal", "Invalid timestamp"))
self._created_at_edit_map.attr_map = {None: "error-edit-normal"}
self._created_at_edit_map.focus_map = {None: "error-edit-focus"}
self._created_at_box_map.attr_map = {None: "error-box-normal"}
self._created_at_box_map.focus_map = {None: "error-box-focus"}
else:
self._created_at_edit_map.attr_map = {None: None}
self._created_at_edit_map.focus_map = {None: None}
self._created_at_box_map.attr_map = {None: None}
self._created_at_box_map.focus_map = {None: None}
if not text:
text_text = rows_calculating_delegate_widget[2]
text_text.set_text(("error-message-normal", "Twt text is empty"))
self._text_edit_map.attr_map = {None: "error-edit-normal"}
self._text_edit_map.focus_map = {None: "error-edit-focus"}
self._text_box_map.attr_map = {None: "error-box-normal"}
self._text_box_map.focus_map = {None: "error-box-focus"}
else:
self._text_edit_map.attr_map = {None: None}
self._text_edit_map.focus_map = {None: None}
self._text_box_map.attr_map = {None: None}
self._text_box_map.focus_map = {None: None}
self._publish.set_selectable(not created_at_error and text)
return preview_widgets, rows_calculating_delegate_widget
......
......@@ -74,8 +74,12 @@ class Renderer:
entry("subject-focus", "black", "dark red"),
entry("link-normal", "light blue,underline", "black"),
entry("link-focus", "light blue,underline", "white"),
entry("error-normal", "white,bold", "dark red"),
entry("error-focus", "dark red,bold", "white"),
entry("error-message-normal", "white,bold", "dark red"),
entry("error-message-focus", "dark red,bold", "white"),
entry("error-edit-normal", "dark red,bold", "white"),
entry("error-edit-focus", "white,bold", "dark red"),
entry("error-box-normal", "dark red", "black"),
entry("error-box-focus", "light red", "black"),
entry("publish-normal", "white", "black"),
entry("publish-focus", "black", "white"),
entry("publish-disabled", "dark gray", "black"),
......@@ -90,7 +94,9 @@ class Renderer:
"mentioned-nick-normal": "mentioned-nick-focus",
"subject-normal": "subject-focus",
"link-normal": "link-focus",
"error-normal": "error-focus",
"error-message-normal": "error-message-focus",
"error-edit-normal": "error-edit-focus",
"error-box-normal": "error-box-focus",
"publish-normal": "publish-focus",
"cancel-normal": "cancel-focus",
}
......
......@@ -579,3 +579,27 @@ class SelectableProxyAttrMap(urwid.AttrMap):
self._original_widget.set_selectable(selectable)
self.set_attr_map(self._normal_map if selectable else self._disabled_map)
class CachingAttrMap(urwid.AttrMap):
"""
An attribute map with caches the maps and only updates them when they
actually differ.
Yes, this name is misleading, but currently I'm too tired to come up with a
better one.
"""
def get_attr_map(self):
return super().get_attr_map()
def set_attr_map(self, attr_map):
if not hasattr(self, "_attr_map") or self.attr_map != attr_map:
super().set_attr_map(attr_map)
attr_map = property(get_attr_map, set_attr_map)
def get_focus_map(self):
return super().get_focus_map()
def set_focus_map(self, focus_map):
if not hasattr(self, "_focus_map") or self.focus_map != focus_map:
super().set_focus_map(focus_map)
focus_map = property(get_focus_map, set_focus_map)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment