Add a built-in Micron page/file editor under [ My Node ], extend Micron with nestable collapsible section folding and multi-row form inputs, misc changes

This commit is contained in:
zenith 2026-06-08 05:00:46 -04:00
parent 997bae397c
commit 61812aa6ae
10 changed files with 1459 additions and 61 deletions

View file

@ -25,6 +25,8 @@ An masked field : `B444`<!|password`Value of Field>`b
An small field : `B444`<8|small`test>`b, and some more text.
A multi-row field : `B444`<40x4|notes`>`b
Two fields : `B444`<8|one`One>`b `B444`<8|two`Two>`b
The data can be `!`[submitted`:/page/input_fields.mu`username|two]`!.

View file

@ -169,6 +169,10 @@ GLYPHS = {
("audio", "[~]", "\u266b", "\uf1c7"),
("pin", "*", "\u2605", "\uf08d"),
("copy", "[C]", "\u29c9", "\uf0c5"),
("folder", "[+]", "\u25b8", "\uf07b"),
("folder_open", "[-]", "\u25be", "\uf07c"),
("fold_open", "-", "▾", "▾"),
("fold_closed", "+", "▸", "▸"),
}
class TextUI:

View file

@ -347,6 +347,8 @@ class Browser:
if target_idx is None:
return
self.reveal_index(int(target_idx))
row_offset = self._rows_above(int(target_idx), cols)
try:
@ -363,7 +365,7 @@ class Browser:
browser_cols = screen_cols-lw-2
cols = browser_cols
except Exception:
except Exception as e:
RNS.log(f"Could not calculate browser width, anchors will be inaccurate: {e}", RNS.LOG_WARNING)
RNS.trace_exception(e)
cols = 100
@ -373,8 +375,10 @@ class Browser:
def _rows_above(self, index, cols):
if index <= 0 or not self.attr_maps: return 0
hidden = self.hidden_indices()
total = 0
for i in range(min(index, len(self.attr_maps))):
if i in hidden: continue
try: total += self.attr_maps[i].rows((cols,))
except Exception: total += 1
@ -627,6 +631,106 @@ class Browser:
self.page_partials = {}
self.browser_body = urwid.AttrMap(ScrollBar(Scrollable(pile, force_forward_keypress=True), thumb_char="\u2503", trough_char=" "), "scrollbar")
self.detect_partials()
self.init_folds()
# render an arbitrary markup buffer (used by the page editor preview) and
# return the content widget, with partials/folds/fields wired as usual
def render_markup_buffer(self, markup):
self.status = Browser.DONE
self.markup = markup
self.page_background_color = None
self.page_foreground_color = None
for tag, attr in (("#!bg=", "page_background_color"), ("#!fg=", "page_foreground_color")):
pos = markup.find(tag)
if pos >= 0:
endpos = markup.find("\n", pos)
if endpos < 0: endpos = len(markup)
if endpos-(pos+len(tag)) in (3, 6):
setattr(self, attr, markup[pos+len(tag):endpos])
try:
self.attr_maps = markup_to_attrmaps(strip_modifiers(markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
except Exception as e:
self.attr_maps = [urwid.AttrMap(urwid.Text(f"Could not render preview: {e}"), "browser_inactive")]
self.update_page_display()
return self.browser_body
def init_folds(self):
self.fold_sections = {}
self.collapsed_headings = set()
attrmaps = self.attr_maps
header_levels = getattr(attrmaps, "header_levels", None) or {}
row_levels = getattr(attrmaps, "row_levels", None) or {}
collapsibles = getattr(attrmaps, "collapsibles", None) or {}
if not collapsibles:
return
n = len(attrmaps)
for idx in sorted(collapsibles.keys()):
widget = self.collapsible_widget_at(idx)
if widget is None: continue
depth = header_levels.get(idx, 1)
end = n
for i in range(idx+1, n):
if row_levels.get(i, depth) < depth or \
(i in header_levels and header_levels.get(i, 1) <= depth):
end = i; break
self.fold_sections[widget] = (idx+1, end)
if collapsibles[idx]:
self.collapsed_headings.add(widget)
try: widget.set_collapsed(True)
except Exception: pass
if self.collapsed_headings:
self.rebuild_visible()
def collapsible_widget_at(self, idx):
try:
o = self.attr_maps[idx]._original_widget
if getattr(o, "is_collapsible_heading", False): return o
except Exception: pass
return None
def hidden_indices(self):
hidden = set()
for widget in getattr(self, "collapsed_headings", ()):
rng = getattr(self, "fold_sections", {}).get(widget)
if rng: hidden.update(range(rng[0], rng[1]))
return hidden
def rebuild_visible(self):
pile = getattr(self, "page_pile", None)
if pile is None: return
hidden = self.hidden_indices()
opts = pile.options()
pile.contents = [(w, opts) for i, w in enumerate(self.attr_maps) if i not in hidden]
try:
if pile.focus_position >= len(pile.contents):
pile.focus_position = max(0, len(pile.contents)-1)
except Exception: pass
def fold_changed(self, heading_widget):
if getattr(heading_widget, "collapsed", False):
self.collapsed_headings.add(heading_widget)
else:
self.collapsed_headings.discard(heading_widget)
self.rebuild_visible()
try:
for pos, (w, _) in enumerate(self.page_pile.contents):
if getattr(w, "_original_widget", None) is heading_widget:
self.page_pile.focus_position = pos
break
except Exception: pass
def reveal_index(self, idx):
changed = False
for widget in list(getattr(self, "collapsed_headings", ())):
rng = getattr(self, "fold_sections", {}).get(widget)
if rng and rng[0] <= idx < rng[1]:
self.collapsed_headings.discard(widget)
try: widget.set_collapsed(False)
except Exception: pass
changed = True
if changed: self.rebuild_visible()
def parse_url(self, url):
path = None

View file

@ -20,7 +20,7 @@ from nomadnet.util import sanitize_name
from RNS.Utilities.rngit.util import MarkdownToMicron
from RNS.Utilities.rngit.highlight import SyntaxHighlighter
from .MicronParser import markup_to_attrmaps
from .Helpers import ClickableIcon, osc52_copy
from .Helpers import ClickableIcon, osc52_copy, qr_ascii
from .ReadlineEdit import ReadlineMixin, ReadlineEdit
from nomadnet.util import strip_modifiers, strip_micron, strip_escaped_micron, unescape_micron, strip_non_formatting_tags
from nomadnet.ui import THEME_DARK, THEME_LIGHT
@ -639,22 +639,7 @@ class ConversationsDisplay():
self.show_qr_dialog(addr, title=display)
def show_qr_dialog(self, data, title=None):
qr_text = None
try:
import qrcode
try:
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=1, border=1)
qr.add_data(data)
qr.make()
import io
buf = io.StringIO()
qr.print_ascii(out=buf, invert=False)
qr_text = buf.getvalue().rstrip("\n")
except Exception as e:
RNS.log("QR generation failed: "+str(e), RNS.LOG_ERROR)
qr_text = None
except Exception:
qr_text = None
qr_text = qr_ascii(data)
def dismiss(_b):
self._restore_listbox_pane()

1053
nomadnet/ui/textui/Editor.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1420,6 +1420,7 @@ With micron you can easily create structured documents and pages with formatting
`F79d`_`[A Few Demo Outputs`#a-few-demo-outputs]`_`f
`F79d`_`[Micron Tags`#micron-tags]`_`f
`F79d`_`[High Level Stuff`#high-level-stuff]`_`f
`F79d`_`[Collapsible Sections`#collapsible-sections]`_`f
`F79d`_`[Colors`#colors]`_`f
`F79d`_`[Page Foreground and Background Colors`#page-foreground-and-background-colors]`_`f
`F79d`_`[Links`#links]`_`f
@ -1587,6 +1588,32 @@ A sub sub section. We could continue, but you get the point.
Wait! It's worth noting that we can also create sections without headings. They look like this.
``
>Collapsible Sections
Any section heading can be made collapsible, turning it into an accordion the reader can fold away and reveal on demand. Prefix a heading with \\`+ to make it foldable and open by default, or with \\`- to make it foldable but start collapsed:
`Faaa
`=
`+>Open by default
This text can be folded away by selecting the heading.
`->Hidden until expanded
This starts collapsed, and appears when the reader expands it.
`=
``
A collapsible heading folds everything beneath it, up to the next heading of the same or a higher level, exactly like a normal section. It can therefore contain anything: text, tables, input fields, sub-sections, and even live partials.
To fold or unfold a section, move onto its heading and press Enter or Space, or click it with the mouse. Open sections are marked with ▾ and collapsed ones with ▸.
You can choose your own indicators for a page by placing a #!fold directive near the top, giving the open glyph followed by the collapsed glyph:
`Faaa
`=
#!fold [-] [+]
`=
``
>Colors
@ -1781,6 +1808,14 @@ A sized input field: `B444`<16|with_size`>`b
`=
``
You can also reserve more than one row for a field by appending `!xROWS`! to the width, which is useful for longer, multi-line input. This creates a field that is 40 columns wide and 5 rows tall:
`Faaa
`=
A multi-row input field: `B444`<40x5|notes`>`b
`=
``
It is possible to mask fields, for example for use with passwords and similar:
`Faaa
@ -1809,6 +1844,8 @@ A sized input field: `B444`<16|with_size`>`B333
A masked input field: `B444`<!|masked_demo`hidden text>`B333
A multi-row input field: `B444`<40x5|notes`>`B333
Full control: `B444`<!32|all_options`hidden text>`B333
`b

View file

@ -17,6 +17,21 @@ def osc52_copy(text):
return False
def qr_ascii(data):
try:
import qrcode
import io
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=1, border=1)
qr.add_data(data)
qr.make()
buf = io.StringIO()
qr.print_ascii(out=buf, invert=False)
return buf.getvalue().rstrip("\n")
except Exception as e:
RNS.log("QR generation failed: "+str(e), RNS.LOG_ERROR)
return None
class ClickableIcon(urwid.Text):
_selectable = False

View file

@ -9,6 +9,7 @@ from .Interfaces import *
from .Map import *
from .Log import *
from .Guide import *
from .Editor import *
import urwid
class SubDisplays():
@ -23,6 +24,7 @@ class SubDisplays():
self.map_display = MapDisplay(self.app)
self.log_display = LogDisplay(self.app)
self.guide_display = GuideDisplay(self.app)
self.page_editor_display = PageEditorDisplay(self.app) if app.enable_node else None
if app.firstrun:
self.active_display = self.guide_display
@ -136,6 +138,14 @@ class MainDisplay():
self.sub_displays.active_display = self.sub_displays.guide_display
self.update_active_sub_display()
def show_page_editor(self, user_data):
if not self.app.enable_node:
return
self.sub_displays.active_display = self.sub_displays.page_editor_display
self.update_active_sub_display()
self.sub_displays.page_editor_display.start()
self.frame.focus_position = "body"
def update_active_sub_display(self):
self.frame.contents["body"] = (self.sub_displays.active().widget, None)
self.update_active_shortcuts()
@ -195,13 +205,17 @@ class MenuDisplay():
button_config = (10, MenuButton("Config", on_press=handler.show_config))
button_interfaces = (14, MenuButton("Interfaces", on_press=handler.show_interfaces))
button_guide = (9, MenuButton("Guide", on_press=handler.show_guide))
button_mynode = (11, MenuButton("My Node", on_press=handler.show_page_editor))
button_quit = (8, MenuButton("Quit", on_press=handler.quit))
# buttons = [menu_text, button_conversations, button_node, button_directory, button_map]
if self.app.config["textui"]["hide_guide"]:
buttons = [menu_text, button_conversations, button_network, button_channels, button_log, button_interfaces, button_config, button_quit]
else:
buttons = [menu_text, button_conversations, button_network, button_channels, button_log, button_interfaces, button_config, button_guide, button_quit]
buttons = [menu_text, button_conversations, button_network]
if self.app.enable_node:
buttons.append(button_mynode)
buttons += [button_channels, button_log, button_interfaces, button_config]
if not self.app.config["textui"]["hide_guide"]:
buttons.append(button_guide)
buttons.append(button_quit)
columns = MenuColumns(buttons, dividechars=1)
columns.handler = handler

View file

@ -36,6 +36,15 @@ SECTION_INDENT = 2
INDENT_RIGHT = 1
MAX_TABLE_WIDTH = 100
DEFAULT_FOLD_GLYPHS = ("▾", "▸")
def default_fold_glyphs():
try:
g = nomadnet.NomadNetworkApp.get_shared_instance().ui.glyphs
return (g.get("fold_open", DEFAULT_FOLD_GLYPHS[0]), g.get("fold_closed", DEFAULT_FOLD_GLYPHS[1]))
except Exception:
return DEFAULT_FOLD_GLYPHS
def default_state(fg=None, bg=None):
global SELECTED_STYLES
ensure_selected_styles()
@ -132,15 +141,26 @@ def markup_to_attrmaps(markup, url_delegate = None, fg_color=None, bg_color=None
if state.get("_header_pending"):
state["header_rows"].append(row_index)
state.setdefault("header_levels", {})[row_index] = state["depth"]
cp = state.get("_collapsible_pending")
if cp is not None:
state.setdefault("collapsibles", {})[row_index] = bool(cp["collapsed"])
state["_collapsible_pending"] = None
state["_header_pending"] = False
row_levels = state.setdefault("row_levels", {})
for display_widget in display_widgets:
row_levels[len(attrmaps)] = state["depth"]
attrmap = urwid.AttrMap(display_widget, make_style(state))
attrmaps.append(attrmap)
try:
setattr(attrmaps, "anchors", anchors)
setattr(attrmaps, "header_rows", list(state.get("header_rows", [])))
setattr(attrmaps, "header_levels", dict(state.get("header_levels", {})))
setattr(attrmaps, "row_levels", dict(state.get("row_levels", {})))
setattr(attrmaps, "collapsibles", dict(state.get("collapsibles", {})))
setattr(attrmaps, "fold_glyphs", state.get("fold_glyphs") or default_fold_glyphs())
except Exception:
pass
@ -222,6 +242,7 @@ class FormColumns(urwid.Columns):
MIN_FIELD_WIDTH = 4
@staticmethod
def _natural_width(widget, maxcol, focused):
# mirror the width urwid.Columns itself computes for a PACK column
if not isinstance(widget, urwid.Widget):
@ -297,6 +318,95 @@ class FormColumns(urwid.Columns):
return widths
class MicronField(ReadlineEdit):
def __init__(self, *args, min_rows=1, **kwargs):
try: self._min_rows = max(1, int(min_rows))
except (TypeError, ValueError): self._min_rows = 1
super().__init__(*args, **kwargs)
def rows(self, size, focus=False):
return max(self._min_rows, super().rows(size, focus))
def render(self, size, focus=False):
canv = super().render(size, focus)
if canv.rows() < self._min_rows:
canv = urwid.CompositeCanvas(canv)
canv.pad_trim_top_bottom(0, self._min_rows - canv.rows())
return canv
class CollapsibleHeading(urwid.WidgetWrap):
_selectable = True
ignore_focus = False
def __init__(self, segments, align, heading_style, depth,
collapsed=False, glyphs=None, delegate=None):
self._segments = list(segments)
self.align = align
self.heading_style = heading_style
self.depth = depth
self.collapsed = bool(collapsed)
self.glyphs = glyphs if glyphs is not None else default_fold_glyphs()
self.delegate = delegate
self.is_collapsible_heading = True
super().__init__(self._build())
def selectable(self):
return True
def _glyph(self):
return self.glyphs[1] if self.collapsed else self.glyphs[0]
def _glyph_col(self):
if self._segments and isinstance(self._segments[0], str):
return len(self._segments[0])
return 0
def _build(self):
segs = list(self._segments)
marker = self._glyph() + " "
if segs and isinstance(segs[0], str):
segs.insert(1, marker)
else:
segs.insert(0, marker)
return urwid.AttrMap(urwid.Text(segs, align=self.align), self.heading_style)
def set_collapsed(self, collapsed):
collapsed = bool(collapsed)
if collapsed != self.collapsed:
self.collapsed = collapsed
self._w = self._build()
self._invalidate()
def toggle(self):
self.collapsed = not self.collapsed
self._w = self._build()
self._invalidate()
if self.delegate is not None and hasattr(self.delegate, "fold_changed"):
self.delegate.fold_changed(self)
def keypress(self, size, key):
if self._command_map[key] == urwid.ACTIVATE or key == " ":
self.toggle()
return None
return key
def mouse_event(self, size, event, button, x, y, focus):
if button == 1 and is_mouse_press(event):
self.toggle()
return True
return False
def render(self, size, focus=False):
canv = super().render(size, focus)
if focus:
canv = urwid.CompositeCanvas(canv)
gx = self._glyph_col()
if self.align == "left" and gx < size[0]:
canv.cursor = (gx, 0)
return canv
def parse_line(line, state, url_delegate):
pre_escape = False
if len(line) > 0:
@ -309,6 +419,14 @@ def parse_line(line, state, url_delegate):
# Only parse content if not in literal state
if not state["literal"]:
collapsible_heading = False
collapsed_initial = False
if (line.startswith("`+") or line.startswith("`-")) and line[2:3] == ">":
collapsible_heading = True
collapsed_initial = line[1] == "-"
line = line[2:]
first_char = line[0]
# Apply markup sanitization
if first_char == ">" and "`<" in line:
# Remove heading status from lines containing fields
@ -322,6 +440,10 @@ def parse_line(line, state, url_delegate):
# Check for comments
elif first_char == "#":
parts = line.split()
if parts and parts[0] == "#!fold":
if len(parts) >= 3: state["fold_glyphs"] = (parts[1], parts[2])
elif len(parts) == 2: state["fold_glyphs"] = (parts[1], parts[1])
return None
# Check for tables
@ -395,6 +517,11 @@ def parse_line(line, state, url_delegate):
heading_style = first_style
output.insert(0, " "*left_indent(state))
if collapsible_heading:
state["_collapsible_pending"] = {"collapsed": collapsed_initial}
glyphs = state.get("fold_glyphs") or default_fold_glyphs()
return [CollapsibleHeading(output, state["align"], heading_style,
state["depth"], collapsed_initial, glyphs, url_delegate)]
return [urwid.AttrMap(urwid.Text(output, align=state["align"]), heading_style)]
else:
return None
@ -440,8 +567,9 @@ def parse_line(line, state, url_delegate):
fd = o["data"]
fn = o["name"]
fs = o["style"]
frows = o.get("rows", 1)
fmask = "*" if o["masked"] else None
f = ReadlineEdit(caption="", edit_text=fd, align=state["align"], multiline=True, mask=fmask)
f = MicronField(caption="", edit_text=fd, align=state["align"], multiline=True, mask=fmask, min_rows=frows)
f.field_name = fn
fa = urwid.AttrMap(f, fs)
widgets.append((fw, fa))
@ -759,11 +887,12 @@ def make_output(state, line, url_delegate, pre_escape=False):
field_content = line[field_start:backtick_pos]
field_masked = False
field_width = 24
field_rows = 1
field_type = "field"
field_name = field_content
field_value = ""
field_data = ""
field_prechecked = False
field_prechecked = False
# check if field_content contains '|'
if '|' in field_content:
@ -784,10 +913,16 @@ def make_output(state, line, url_delegate, pre_escape=False):
# Handle field width
if len(field_flags) > 0:
width_flag, _, rows_flag = field_flags.partition("x")
try:
field_width = min(int(field_flags), 256)
field_width = min(int(width_flag), 256)
except ValueError:
pass # Ignore invalid width
if rows_flag:
try:
field_rows = max(1, min(int(rows_flag), 256))
except ValueError:
pass
# Check for value and pre-checked flag
if len(f_components) > 2:
@ -831,6 +966,7 @@ def make_output(state, line, url_delegate, pre_escape=False):
"type": "field",
"name": field_name,
"width": field_width,
"rows": field_rows,
"masked": field_masked,
"data": field_data,
"style": make_style(state)

View file

@ -11,6 +11,7 @@ from nomadnet.util import sanitize_name
from .Browser import Browser
from .ReadlineEdit import ReadlineEdit
from .Helpers import qr_ascii
class NetworkDisplayShortcuts():
def __init__(self, app):
@ -26,7 +27,17 @@ class NetworkDisplayShortcuts():
class DialogLineBox(urwid.LineBox):
def keypress(self, size, key):
if key == "esc":
self.delegate.update_conversation_list()
delegate = getattr(self, "delegate", None)
if delegate is not None and hasattr(delegate, "update_conversation_list"):
delegate.update_conversation_list()
elif delegate is not None and getattr(delegate, "parent", None) is not None:
try:
delegate.dialog_open = False
options = delegate.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
delegate.parent.left_pile.contents[1] = (type(delegate)(delegate.app, delegate.parent), options)
except Exception:
pass
return None
else:
return super(DialogLineBox, self).keypress(size, key)
@ -82,7 +93,7 @@ class AnnounceInfo(urwid.WidgetWrap):
is_node = False
is_pn = False
if info_type == "node" or info_type == True:
type_string = "Nomad Network Node " + g["node"]
type_string = "NomadNet Node " + g["node"]
is_node = True
elif info_type == "pn":
type_string = "LXMF Propagation Node " + g["sent"]
@ -634,7 +645,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
lxmf_addr_str = "No associated Propagation Node known"
type_string = "Nomad Network Node " + g["node"]
type_string = "NomadNet Node " + g["node"]
if trust_level == DirectoryEntry.UNTRUSTED:
trust_str = "Untrusted"
@ -1331,22 +1342,27 @@ class LocalPeer(urwid.WidgetWrap):
announce_button = urwid.Button("Announce Now", on_press=announce_query)
self.display_widget = urwid.Pile(
[
t_id,
i_id,
e_name,
urwid.Divider(g["divider1"]),
self.t_last_announce,
announce_button,
urwid.Divider(g["divider1"]),
urwid.Columns([
(urwid.WEIGHT, 0.45, urwid.Button("Save", on_press=save_query)),
(urwid.WEIGHT, 0.1, urwid.Text("")),
(urwid.WEIGHT, 0.45, urwid.Button("Node Info", on_press=node_info_query)),
]),
]
)
peer_widgets = [
t_id,
i_id,
e_name,
urwid.Divider(g["divider1"]),
self.t_last_announce,
announce_button,
urwid.Divider(g["divider1"]),
urwid.Columns([
(urwid.WEIGHT, 0.45, urwid.Button("Save", on_press=save_query)),
(urwid.WEIGHT, 0.1, urwid.Text("")),
(urwid.WEIGHT, 0.45, urwid.Button("Node Info", on_press=node_info_query)),
]),
]
if self.app.enable_node:
peer_widgets.append(
urwid.AttrMap(urwid.Text(" "+g["node"]+" Node Online", align=urwid.LEFT), "shortcutbar")
)
self.display_widget = urwid.Pile(peer_widgets)
super().__init__(urwid.LineBox(self.display_widget, title="Local Peer Info"))
@ -1420,6 +1436,37 @@ class NodeInfo(urwid.WidgetWrap):
def connect_query(sender):
self.parent.browser.retrieve_url(RNS.hexrep(self.app.node.destination.hash, delimit=False))
def edit_query(sender):
self.app.ui.main_display.show_page_editor(None)
def show_qr_query(sender):
def dismiss_dialog(sender):
self.dialog_open = False
options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
self.parent.left_pile.contents[1] = (NodeInfo(self.app, self.parent), options)
dest_addr = RNS.hexrep(self.app.node.destination.hash, delimit=False)
qr_text = qr_ascii(dest_addr)
rows = [urwid.Text("")]
if qr_text is not None:
rows.append(urwid.Text(qr_text, align=urwid.CENTER))
else:
rows.append(urwid.Text("Node destination address:", align=urwid.CENTER))
rows += [
urwid.Text(""),
urwid.Text("< "+dest_addr+" >", align=urwid.CENTER),
urwid.Text(""),
urwid.Button("Close", on_press=dismiss_dialog),
]
dialog = DialogLineBox(urwid.Pile(rows), title="Node Destination")
dialog.delegate = self
self.dialog_open = True
options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
self.parent.left_pile.contents[1] = (dialog, options)
if NodeInfo.announce_timer == None:
self.t_last_announce = NodeAnnounceTime(self.app)
NodeInfo.announce_timer = self.t_last_announce
@ -1467,7 +1514,9 @@ class NodeInfo(urwid.WidgetWrap):
announce_button = urwid.Button("Announce", on_press=announce_query)
connect_button = urwid.Button("Browse", on_press=connect_query)
edit_button = urwid.Button("Edit", on_press=edit_query)
reset_button = urwid.Button("Rst Stats", on_press=stats_query)
qr_button = urwid.Button("Show Destination", on_press=show_qr_query)
if not self.app.disable_propagation:
pile = urwid.Pile([
@ -1483,14 +1532,13 @@ class NodeInfo(urwid.WidgetWrap):
self.t_total_pages,
self.t_total_files,
urwid.Divider(g["divider1"]),
urwid.Columns([
(urwid.WEIGHT, 5, urwid.Button("Back", on_press=show_peer_info)),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 6, connect_button),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 8, reset_button),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 7, announce_button),
urwid.Pile([
urwid.Button("Peer Info", on_press=show_peer_info),
connect_button,
edit_button,
reset_button,
announce_button,
qr_button,
])
])
else:
@ -1505,14 +1553,13 @@ class NodeInfo(urwid.WidgetWrap):
self.t_total_pages,
self.t_total_files,
urwid.Divider(g["divider1"]),
urwid.Columns([
(urwid.WEIGHT, 5, urwid.Button("Back", on_press=show_peer_info)),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 6, connect_button),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 8, reset_button),
(urwid.WEIGHT, 0.5, urwid.Text("")),
(urwid.WEIGHT, 7, announce_button),
urwid.Pile([
urwid.Button("Peer Info", on_press=show_peer_info),
connect_button,
edit_button,
reset_button,
announce_button,
qr_button,
])
])
else:
@ -1640,10 +1687,11 @@ class NetworkDisplay():
self.known_nodes_display.delegate = self
self.list_display = 1
default_info_display = self.node_info_display if self.app.enable_node else self.local_peer_display
self.left_pile = NetworkLeftPile([
(urwid.WEIGHT, 1, self.known_nodes_display),
# (urwid.PACK, self.network_stats_display),
(urwid.PACK, self.local_peer_display),
(urwid.PACK, default_info_display),
])
self.left_pile.parent = self