From a5aa2097bd7370c3c16bb1fb670b195f42c9b25e Mon Sep 17 00:00:00 2001 From: probability Date: Mon, 25 Nov 2024 18:15:42 -0500 Subject: [PATCH 1/2] Add Checkbox and Radio Button fields to Micron format --- nomadnet/examples/various/input_fields.py | 21 +++- nomadnet/ui/textui/Browser.py | 58 +++++++--- nomadnet/ui/textui/Guide.py | 37 ++++++ nomadnet/ui/textui/MicronParser.py | 133 +++++++++++++++------- 4 files changed, 193 insertions(+), 56 deletions(-) diff --git a/nomadnet/examples/various/input_fields.py b/nomadnet/examples/various/input_fields.py index 43d7e65..663dc93 100644 --- a/nomadnet/examples/various/input_fields.py +++ b/nomadnet/examples/various/input_fields.py @@ -17,6 +17,8 @@ -= + +>>>Text Fields An input field : `B444``b An masked field : `B444``b @@ -27,7 +29,24 @@ The data can be `!`[submitted`:/page/input_fields.mu`username|two]`!. -You can `!`[submit`:/page/input_fields.mu`one|password|small]`! other fields, or just `!`[a single one`:/page/input_fields.mu`username]`! +>> Checkbox Fields + +`B444``b Sign me up + +>> Radio group + +Select your favorite color: + +`B900`<^|color|Red`>`b Red + +`B090`<^|color|Green`>`b Green + +`B009`<^|color|Blue`>`b Blue + + +>>> Submitting data + +You can `!`[submit`:/page/input_fields.mu`one|password|small|color]`! other fields, or just `!`[a single one`:/page/input_fields.mu`username]`! Or simply `!`[submit them all`:/page/input_fields.mu`*]`!. diff --git a/nomadnet/ui/textui/Browser.py b/nomadnet/ui/textui/Browser.py index 58ae008..5e412a4 100644 --- a/nomadnet/ui/textui/Browser.py +++ b/nomadnet/ui/textui/Browser.py @@ -180,23 +180,47 @@ def handle_link(self, link_target, link_data = None): else: link_fields.append(e) - def recurse_down(w): - target = None - if isinstance(w, list): - for t in w: - recurse_down(t) - elif isinstance(w, tuple): - for t in w: - recurse_down(t) - elif hasattr(w, "contents"): - recurse_down(w.contents) - elif hasattr(w, "original_widget"): - recurse_down(w.original_widget) - elif hasattr(w, "_original_widget"): - recurse_down(w._original_widget) - else: - if hasattr(w, "field_name") and (all_fields or w.field_name in link_fields): - request_data["field_"+w.field_name] = w.get_edit_text() + def recurse_down(w): + if isinstance(w, list): + for t in w: + recurse_down(t) + elif isinstance(w, tuple): + for t in w: + recurse_down(t) + elif hasattr(w, "contents"): + recurse_down(w.contents) + elif hasattr(w, "original_widget"): + recurse_down(w.original_widget) + elif hasattr(w, "_original_widget"): + recurse_down(w._original_widget) + else: + if hasattr(w, "field_name") and (all_fields or w.field_name in link_fields): + field_key = "field_" + w.field_name + if isinstance(w, urwid.Edit): + request_data[field_key] = w.edit_text + elif isinstance(w, urwid.RadioButton): + if w.state: + user_data = getattr(w, "field_value", None) + if user_data is not None: + request_data[field_key] = user_data + elif isinstance(w, urwid.CheckBox): + user_data = getattr(w, "field_value", "1") + if w.state: + existing_value = request_data.get(field_key, '') + if existing_value: + # Concatenate the new value with the existing one + request_data[field_key] = existing_value + ',' + user_data + else: + # Initialize the field with the current value + request_data[field_key] = user_data + else: + pass # do nothing if checkbox is not check + + + + + + recurse_down(self.attr_maps) RNS.log("Including request data: "+str(request_data), RNS.LOG_DEBUG) diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py index 69305cf..48dfb56 100644 --- a/nomadnet/ui/textui/Guide.py +++ b/nomadnet/ui/textui/Guide.py @@ -1152,6 +1152,43 @@ def focus_reader(self): A masked input field: `B444``B333 Full control: `B444``B333 +`b +>>> Checkboxes + +In addition to text fields, Checkboxes are another way of submitting data. They allow the user to make a single selection or select multiple options. + +`Faaa +`= +``b Label Text` +`= +When the checkbox is checked, it's field will be set to the provided value. If there are multiple checkboxes that share the same field name, the checked values will be concatenated when they are sent to the node by a comma. +`` + +`B444``b Sign me up` + +>>> Radio groups + +Radio groups are another input that lets the user chose from a set of options. Unlike checkboxes, radio buttons with the same field name are mutually exclusive. + +Example: + +`= +`B900`<^|color|Red`>`b Red + +`B090`<^|color|Green`>`b Green + +`B009`<^|color|Blue`>`b Blue +`= + +will render: + +`B900`<^|color|Red`>`b Red + +`B090`<^|color|Green`>`b Green + +`B009`<^|color|Blue`>`b Blue + +In this example, when the data is submitted, `B444` field_color`b will be set to whichever value from the list was selected. `` diff --git a/nomadnet/ui/textui/MicronParser.py b/nomadnet/ui/textui/MicronParser.py index 27e420b..7668ecf 100644 --- a/nomadnet/ui/textui/MicronParser.py +++ b/nomadnet/ui/textui/MicronParser.py @@ -160,7 +160,6 @@ def parse_line(line, state, url_delegate): tw.in_columns = True else: tw = urwid.Text(o, align=state["align"]) - widgets.append((urwid.PACK, tw)) else: if o["type"] == "field": @@ -169,10 +168,37 @@ def parse_line(line, state, url_delegate): fn = o["name"] fs = o["style"] fmask = "*" if o["masked"] else None - f = urwid.Edit(caption="", edit_text=fd, align=state["align"], multiline=True, mask=fmask) + f = urwid.Edit(caption="", edit_text=fd, align=state["align"], multiline=False, mask=fmask) f.field_name = fn fa = urwid.AttrMap(f, fs) widgets.append((fw, fa)) + elif o["type"] == "checkbox": + fn = o["name"] + fv = o["value"] + flabel = o["label"] + fs = o["style"] + f = urwid.CheckBox(flabel, state=False) + f.field_name = fn + f.field_value = fv + fa = urwid.AttrMap(f, fs) + widgets.append((urwid.PACK, fa)) + elif o["type"] == "radio": + fn = o["name"] + fv = o["value"] + flabel = o["label"] + fs = o["style"] + if not "radio_groups" in state: + state["radio_groups"] = {} + if not fn in state["radio_groups"]: + state["radio_groups"][fn] = [] + group = state["radio_groups"][fn] + f = urwid.RadioButton(group, flabel, state=False, user_data=fv) + f.field_name = fn + f.field_value = fv + fa = urwid.AttrMap(f, fs) + widgets.append((urwid.PACK, fa)) + + columns_widget = urwid.Columns(widgets, dividechars=0) text_widget = columns_widget @@ -458,54 +484,85 @@ def make_output(state, line, url_delegate): elif c == "a": state["align"] = state["default_align"] - elif c == "<": + elif c == '<': + if len(part) > 0: + output.append(make_part(state, part)) + part = "" try: - field_name = None - field_name_end = line[i:].find("`") - if field_name_end == -1: - pass + field_start = i + 1 # position after '<' + backtick_pos = line.find('`', field_start) + if backtick_pos == -1: + pass # No '`', invalid field else: - field_name = line[i+1:i+field_name_end] - field_name_skip = len(field_name) + field_content = line[field_start:backtick_pos] field_masked = False field_width = 24 - - if "|" in field_name: - f_components = field_name.split("|") + field_type = "field" + field_name = field_content + field_value = "" + field_data = "" + + # check if field_content contains '|' + if '|' in field_content: + f_components = field_content.split('|') field_flags = f_components[0] field_name = f_components[1] - if "!" in field_flags: + + # handle field type indicators + if '^' in field_flags: + field_type = "radio" + field_flags = field_flags.replace("^", "") + elif '?' in field_flags: + field_type = "checkbox" + field_flags = field_flags.replace("?", "") + elif '!' in field_flags: field_flags = field_flags.replace("!", "") field_masked = True + + # Handle field width if len(field_flags) > 0: - field_width = min(int(field_flags), 256) - - def sr(): - return "@{"+str(random.randint(1000,9999))+"}" - rsg = sr() - while rsg in line[i+field_name_end:]: - rsg = sr() - lr = line[i+field_name_end:].replace("\\>", rsg) - endpos = lr.find(">") - - if endpos == -1: - pass - + try: + field_width = min(int(field_flags), 256) + except ValueError: + pass # Ignore invalid width + + if len(f_components) > 2: + field_value = f_components[2] + else: + field_name = field_content + + # Find the closing '>' character + field_end = line.find('>', backtick_pos) + if field_end == -1: + pass # No closing '>', invalid field else: - field_data = lr[1:endpos].replace(rsg, "\\>") - skip = len(field_data)+field_name_skip+2 - field_data = field_data.replace("\\>", ">") - output.append({ - "type":"field", - "name": field_name, - "width": field_width, - "masked": field_masked, - "data": field_data, - "style": make_style(state) - }) + field_data = line[backtick_pos+1:field_end] + + # Now, we have all field data + if field_type in ["checkbox", "radio"]: + # for checkboxes and radios, field_data is the label + output.append({ + "type": field_type, + "name": field_name, + "value": field_value if field_value else field_data, + "label": field_data, + "style": make_style(state) + }) + else: + # For text fields field_data is the initial text + output.append({ + "type": "field", + "name": field_name, + "width": field_width, + "masked": field_masked, + "data": field_data, + "style": make_style(state) + }) + skip = field_end - i except Exception as e: pass - + + elif c == "[": endpos = line[i:].find("]") if endpos == -1: From 912c510ab2df91a436b2c60887900e0fcd060cc2 Mon Sep 17 00:00:00 2001 From: zenith Date: Tue, 26 Nov 2024 19:58:53 -0500 Subject: [PATCH 2/2] Add Checkbox and Radio Button fields to Micron format --- nomadnet/examples/various/input_fields.py | 2 +- nomadnet/ui/textui/Guide.py | 4 ++++ nomadnet/ui/textui/MicronParser.py | 26 +++++++++++++++++++---- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/nomadnet/examples/various/input_fields.py b/nomadnet/examples/various/input_fields.py index 663dc93..5d1eccc 100644 --- a/nomadnet/examples/various/input_fields.py +++ b/nomadnet/examples/various/input_fields.py @@ -31,7 +31,7 @@ >> Checkbox Fields -`B444``b Sign me up +`B444``b Sign me up >> Radio group diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py index 48dfb56..ba6c4d5 100644 --- a/nomadnet/ui/textui/Guide.py +++ b/nomadnet/ui/textui/Guide.py @@ -1166,6 +1166,10 @@ def focus_reader(self): `B444``b Sign me up` +You can also pre-check both checkboxes and radio groups by appending a |* after the field value. + +`B444``b Pre-checked checkbox` + >>> Radio groups Radio groups are another input that lets the user chose from a set of options. Unlike checkboxes, radio buttons with the same field name are mutually exclusive. diff --git a/nomadnet/ui/textui/MicronParser.py b/nomadnet/ui/textui/MicronParser.py index 7668ecf..05ded7a 100644 --- a/nomadnet/ui/textui/MicronParser.py +++ b/nomadnet/ui/textui/MicronParser.py @@ -177,7 +177,8 @@ def parse_line(line, state, url_delegate): fv = o["value"] flabel = o["label"] fs = o["style"] - f = urwid.CheckBox(flabel, state=False) + fprechecked = o.get("prechecked", False) + f = urwid.CheckBox(flabel, state=fprechecked) f.field_name = fn f.field_value = fv fa = urwid.AttrMap(f, fs) @@ -187,12 +188,13 @@ def parse_line(line, state, url_delegate): fv = o["value"] flabel = o["label"] fs = o["style"] - if not "radio_groups" in state: + fprechecked = o.get("prechecked", False) + if "radio_groups" not in state: state["radio_groups"] = {} - if not fn in state["radio_groups"]: + if fn not in state["radio_groups"]: state["radio_groups"][fn] = [] group = state["radio_groups"][fn] - f = urwid.RadioButton(group, flabel, state=False, user_data=fv) + f = urwid.RadioButton(group, flabel, state=fprechecked, user_data=fv) f.field_name = fn f.field_value = fv fa = urwid.AttrMap(f, fs) @@ -200,6 +202,7 @@ def parse_line(line, state, url_delegate): + columns_widget = urwid.Columns(widgets, dividechars=0) text_widget = columns_widget # text_widget = urwid.Text("<"+output+">", align=state["align"]) @@ -501,6 +504,7 @@ def make_output(state, line, url_delegate): field_name = field_content field_value = "" field_data = "" + field_prechecked = False # check if field_content contains '|' if '|' in field_content: @@ -526,10 +530,23 @@ def make_output(state, line, url_delegate): except ValueError: pass # Ignore invalid width + # Check for value and pre-checked flag if len(f_components) > 2: field_value = f_components[2] + else: + field_value = "" + if len(f_components) > 3: + if f_components[3] == '*': + field_prechecked = True + else: + # No '|', so field_name is field_content field_name = field_content + field_type = "field" + field_masked = False + field_width = 24 + field_value = "" + field_prechecked = False # Find the closing '>' character field_end = line.find('>', backtick_pos) @@ -546,6 +563,7 @@ def make_output(state, line, url_delegate): "name": field_name, "value": field_value if field_value else field_data, "label": field_data, + "prechecked": field_prechecked, "style": make_style(state) }) else: