from abc import ABC from owrx.modes import Modes from owrx.config import Config from owrx.form.input.validator import Validator from owrx.form.input.converter import Converter, NullConverter, IntConverter, FloatConverter, EnumConverter from enum import Enum class Input(ABC): def __init__(self, id, label, infotext=None, converter: Converter = None, validator: Validator = None, disabled=False, removable=False): self.id = id self.label = label self.infotext = infotext self.converter = self.defaultConverter() if converter is None else converter self.validator = validator self.disabled = disabled self.removable = removable def setDisabled(self, disabled=True): self.disabled = disabled def setRemovable(self, removable=True): self.removable = removable def defaultConverter(self): return NullConverter() def bootstrap_decorate(self, input): return """
{input} {infotext}
{removebutton}
""".format( id=self.id, label=self.label, input=input, infotext="{text}".format(text=self.infotext) if self.infotext else "", removable="removable" if self.removable else "", removebutton='' if self.removable else "", ) def input_classes(self, errors): classes = ["form-control", "form-control-sm"] if errors: classes.append("is-invalid") return " ".join(classes) def input_properties(self, value, errors): props = { "class": self.input_classes(errors), "id": self.id, "name": self.id, "placeholder": self.label, "value": value, } if self.disabled: props["disabled"] = "disabled" return props def render_input_properties(self, value, error): return " ".join('{}="{}"'.format(prop, value) for prop, value in self.input_properties(value, error).items()) def render_errors(self, errors): return "".join("""
{msg}
""".format(msg=e) for e in errors) def render_input_group(self, value, errors): return """ {input} {errors} """.format( input=self.render_input(value, errors), errors=self.render_errors(errors) ) def render_input(self, value, errors): return "".format(properties=self.render_input_properties(value, errors)) def render(self, config, errors): value = config[self.id] if self.id in config else None error = errors[self.id] if self.id in errors else [] return self.bootstrap_decorate(self.render_input_group(self.converter.convert_to_form(value), error)) def parse(self, data): if self.id in data: value = self.converter.convert_from_form(data[self.id][0]) if self.validator is not None: self.validator.validate(self.id, value) return {self.id: value} return {} def getLabel(self): return self.label class TextInput(Input): def input_properties(self, value, errors): props = super().input_properties(value, errors) props["type"] = "text" return props class NumberInput(Input): def __init__(self, id, label, infotext=None, append="", converter: Converter = None, validator: Validator = None): super().__init__(id, label, infotext, converter=converter, validator=validator) self.step = None self.append = append def defaultConverter(self): return IntConverter() def input_properties(self, value, errors): props = super().input_properties(value, errors) props["type"] = "number" if self.step: props["step"] = self.step return props def render_input_group(self, value, errors): if self.append: append = """
{append}
""".format( append=self.append ) else: append = "" return """
{input} {append} {errors}
""".format( input=self.render_input(value, errors), append=append, errors=self.render_errors(errors) ) class FloatInput(NumberInput): def __init__(self, id, label, infotext=None, converter: Converter = None): super().__init__(id, label, infotext, converter=converter) self.step = "any" def defaultConverter(self): return FloatConverter() class LocationInput(Input): def render_input_group(self, value, errors): return """
{inputs}
{errors}
""".format( id=self.id, rowclass="is-invalid" if errors else "", inputs=self.render_input(value, errors), errors=self.render_errors(errors), key=Config.get()["google_maps_api_key"], ) def render_input(self, value, errors): return "".join(self.render_sub_input(value, id, errors) for id in ["lat", "lon"]) def render_sub_input(self, value, id, errors): return """
""".format( id="{0}-{1}".format(self.id, id), label=self.label, classes=self.input_classes(errors), value=value[id], disabled="disabled" if self.disabled else "", ) def parse(self, data): return {self.id: {k: float(data["{0}-{1}".format(self.id, k)][0]) for k in ["lat", "lon"]}} class TextAreaInput(Input): def render_input(self, value, errors): return """ """.format( id=self.id, classes=self.input_classes(errors), value=value, disabled="disabled" if self.disabled else "", ) class CheckboxInput(Input): def __init__(self, id, checkboxText, infotext=None, converter: Converter = None): super().__init__(id, "", infotext=infotext, converter=converter) self.checkboxText = checkboxText def render_input(self, value, errors): return """
""".format( id=self.id, classes=self.input_classes(errors), checked="checked" if value else "", disabled="disabled" if self.disabled else "", checkboxText=self.checkboxText, ) def input_classes(self, error): classes = ["form-check", "form-control-sm"] if error: classes.append("is-invalid") return " ".join(classes) def parse(self, data): if self.id in data: return {self.id: self.converter.convert_from_form("1" in data[self.id])} return {} def getLabel(self): return self.checkboxText class Option(object): # used for both MultiCheckboxInput and DropdownInput def __init__(self, value, text): self.value = value self.text = text class MultiCheckboxInput(Input): def __init__(self, id, label, options, infotext=None): super().__init__(id, label, infotext=infotext) self.options = options def render_input(self, value, errors): return "".join(self.render_checkbox(o, value, errors) for o in self.options) def checkbox_id(self, option): return "{0}-{1}".format(self.id, option.value) def render_checkbox(self, option, value, errors): return """
""".format( id=self.checkbox_id(option), classes=self.input_classes(errors), checked="checked" if option.value in value else "", checkboxText=option.text, disabled="disabled" if self.disabled else "", ) def parse(self, data): def in_response(option): boxid = self.checkbox_id(option) return boxid in data and data[boxid][0] == "on" return {self.id: [o.value for o in self.options if in_response(o)]} def input_classes(self, error): classes = ["form-check", "form-control-sm"] if error: classes.append("is-invalid") return " ".join(classes) class ServicesCheckboxInput(MultiCheckboxInput): def __init__(self, id, label, infotext=None): services = [Option(s.modulation, s.name) for s in Modes.getAvailableServices()] super().__init__(id, label, services, infotext) class Js8ProfileCheckboxInput(MultiCheckboxInput): def __init__(self, id, label, infotext=None): profiles = [ Option("normal", "Normal (15s, 50Hz, ~16WPM)"), Option("slow", "Slow (30s, 25Hz, ~8WPM"), Option("fast", "Fast (10s, 80Hz, ~24WPM"), Option("turbo", "Turbo (6s, 160Hz, ~40WPM"), ] super().__init__(id, label, profiles, infotext) class DropdownInput(Input): def __init__(self, id, label, options, infotext=None, converter: Converter = None): try: isEnum = issubclass(options, DropdownEnum) except TypeError: isEnum = False if isEnum: self.options = [o.toOption() for o in options] if converter is None: converter = EnumConverter(options) else: self.options = options super().__init__(id, label, infotext=infotext, converter=converter) def render_input(self, value, errors): return """ """.format( classes=self.input_classes(errors), id=self.id, options=self.render_options(value), disabled="disabled" if self.disabled else "", ) def render_options(self, value): options = [ """ """.format( text=o.text, value=o.value, selected="selected" if o.value == value else "", ) for o in self.options ] return "".join(options) class DropdownEnum(Enum): def toOption(self): return Option(self.name, str(self)) class ModesInput(DropdownInput): def __init__(self, id, label): options = [Option(m.modulation, m.name) for m in Modes.getAvailableModes()] super().__init__(id, label, options) class ExponentialInput(Input): def __init__(self, id, label, unit, infotext=None): super().__init__(id, label, infotext=infotext) self.unit = unit def defaultConverter(self): return IntConverter() def input_properties(self, value, errors): props = super().input_properties(value, errors) props["type"] = "number" props["step"] = "any" return props def render_input_group(self, value, errors): append = """
""".format( id=self.id, disabled="disabled" if self.disabled else "", unit=self.unit, ) return """
{input} {append} {errors}
""".format( input=self.render_input(value, errors), append=append, errors=self.render_errors(errors) ) def parse(self, data): exponent_id = "{}-exponent".format(self.id) if self.id in data and exponent_id in data: value = int(float(data[self.id][0]) * 10 ** int(data[exponent_id][0])) return {self.id: value} return {}