implement optional device fields

This commit is contained in:
Jakob Ketterl
2021-02-22 23:49:28 +01:00
parent f8beae5f46
commit 54a34b2084
10 changed files with 213 additions and 51 deletions

View File

@ -6,11 +6,19 @@ from enum import Enum
class Input(ABC):
def __init__(self, id, label, infotext=None, converter: Converter = None):
def __init__(self, id, label, infotext=None, converter: Converter = 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.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()
@ -18,23 +26,47 @@ class Input(ABC):
def bootstrap_decorate(self, input):
infotext = "<small>{text}</small>".format(text=self.infotext) if self.infotext else ""
return """
<div class="form-group row">
<div class="form-group row" data-field="{id}">
<label class="col-form-label col-form-label-sm col-3" for="{id}">{label}</label>
<div class="col-9 p-0">
{input}
{infotext}
<div class="col-9 p-0 removable-group {removable}">
<div class="removeable-item">
{input}
{infotext}
</div>
{removebutton}
</div>
</div>
""".format(
id=self.id, label=self.label, input=input, infotext=infotext
id=self.id,
label=self.label,
input=input,
infotext=infotext,
removable="removable" if self.removable else "",
removebutton='<button class="btn btn-sm btn-danger option-remove-button">Remove</button>'
if self.removable
else "",
)
def input_classes(self):
return " ".join(["form-control", "form-control-sm"])
@abstractmethod
def input_properties(self, value):
props = {
"class": self.input_classes(),
"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):
return " ".join('{}="{}"'.format(prop, value) for prop, value in self.input_properties(value).items())
def render_input(self, value):
pass
return "<input {properties} />".format(properties=self.render_input_properties(value))
def render(self, config):
value = config[self.id] if self.id in config else None
@ -43,14 +75,15 @@ class Input(ABC):
def parse(self, data):
return {self.id: self.converter.convert_from_form(data[self.id][0])} if self.id in data else {}
def getLabel(self):
return self.label
class TextInput(Input):
def render_input(self, value):
return """
<input type="text" class="{classes}" id="{id}" name="{id}" placeholder="{label}" value="{value}">
""".format(
id=self.id, label=self.label, classes=self.input_classes(), value=value
)
def input_properties(self, value):
props = super().input_properties(value)
props["type"] = "text"
return props
class NumberInput(Input):
@ -62,6 +95,13 @@ class NumberInput(Input):
def defaultConverter(self):
return IntConverter()
def input_properties(self, value):
props = super().input_properties(value)
props["type"] = "number"
if self.step:
props["step"] = self.step
return props
def render_input(self, value):
if self.append:
append = """
@ -76,15 +116,11 @@ class NumberInput(Input):
return """
<div class="input-group input-group-sm">
<input type="number" class="{classes}" id="{id}" name="{id}" placeholder="{label}" value="{value}" {step}>
{input}
{append}
</div>
""".format(
id=self.id,
label=self.label,
classes=self.input_classes(),
value=value,
step='step="{0}"'.format(self.step) if self.step else "",
input=super().render_input(value),
append=append,
)
@ -116,13 +152,15 @@ class LocationInput(Input):
def render_sub_input(self, value, id):
return """
<div class="col">
<input type="number" class="{classes}" id="{id}" name="{id}" placeholder="{label}" value="{value}" step="any">
<input type="number" class="{classes}" id="{id}" name="{id}" placeholder="{label}" value="{value}"
step="any" {disabled}>
</div>
""".format(
id="{0}-{1}".format(self.id, id),
label=self.label,
classes=self.input_classes(),
value=value[id],
disabled="disabled" if self.disabled else "",
)
def parse(self, data):
@ -132,9 +170,9 @@ class LocationInput(Input):
class TextAreaInput(Input):
def render_input(self, value):
return """
<textarea class="{classes}" id="{id}" name="{id}" style="height:200px;">{value}</textarea>
<textarea class="{classes}" id="{id}" name="{id}" style="height:200px;" {disabled}>{value}</textarea>
""".format(
id=self.id, classes=self.input_classes(), value=value
id=self.id, classes=self.input_classes(), value=value, disabled="disabled" if self.disabled else ""
)
@ -145,16 +183,17 @@ class CheckboxInput(Input):
def render_input(self, value):
return """
<div class="{classes}">
<input class="form-check-input" type="checkbox" id="{id}" name="{id}" {checked}>
<label class="form-check-label" for="{id}">
{checkboxText}
</label>
</div>
<div class="{classes}">
<input class="form-check-input" type="checkbox" id="{id}" name="{id}" {checked} {disabled}>
<label class="form-check-label" for="{id}">
{checkboxText}
</label>
</div>
""".format(
id=self.id,
classes=self.input_classes(),
checked="checked" if value else "",
disabled="disabled" if self.disabled else "",
checkboxText=self.checkboxText,
)
@ -164,6 +203,9 @@ class CheckboxInput(Input):
def parse(self, data):
return {self.id: self.converter.convert_from_form(self.id in data and data[self.id][0] == "on")}
def getLabel(self):
return self.checkboxText
class Option(object):
# used for both MultiCheckboxInput and DropdownInput
@ -186,7 +228,7 @@ class MultiCheckboxInput(Input):
def render_checkbox(self, option, value):
return """
<div class="{classes}">
<input class="form-check-input" type="checkbox" id="{id}" name="{id}" {checked}>
<input class="form-check-input" type="checkbox" id="{id}" name="{id}" {checked} {disabled}>
<label class="form-check-label" for="{id}">
{checkboxText}
</label>
@ -196,6 +238,7 @@ class MultiCheckboxInput(Input):
classes=self.input_classes(),
checked="checked" if option.value in value else "",
checkboxText=option.text,
disabled="disabled" if self.disabled else "",
)
def parse(self, data):
@ -242,9 +285,12 @@ class DropdownInput(Input):
def render_input(self, value):
return """
<select class="{classes}" id="{id}" name="{id}">{options}</select>
<select class="{classes}" id="{id}" name="{id}" {disabled}>{options}</select>
""".format(
classes=self.input_classes(), id=self.id, options=self.render_options(value)
classes=self.input_classes(),
id=self.id,
options=self.render_options(value),
disabled="disabled" if self.disabled else "",
)
def render_options(self, value):

View File

@ -16,12 +16,12 @@ class GainInput(Input):
return """
<div id="{id}">
<select class="{classes}" id="{id}-select" name="{id}-select">
<select class="{classes}" id="{id}-select" name="{id}-select" {disabled}>
{options}
</select>
<div class="option manual" style="display: none;">
<input type="number" id="{id}-manual" name="{id}-manual" value="{value}" class="{classes}"
placeholder="Manual device gain" step="any">
placeholder="Manual device gain" step="any" {disabled}>
</div>
{stageoption}
</div>
@ -32,6 +32,7 @@ class GainInput(Input):
label=self.label,
options=self.render_options(value),
stageoption="" if self.gain_stages is None else self.render_stage_option(value),
disabled="disabled" if self.disabled else ""
)
def render_options(self, value):
@ -79,15 +80,16 @@ class GainInput(Input):
inputs="".join(
"""
<div class="row">
<div class="col-3">{stage}</div>
<label class="col-form-label col-form-label-sm col-3">{stage}</label>
<input type="number" id="{id}-{stage}" name="{id}-{stage}" value="{value}"
class="col-9 {classes}" placeholder="{stage}" step="any">
class="col-9 {classes}" placeholder="{stage}" step="any" {disabled}>
</div>
""".format(
id=self.id,
stage=stage,
value=value_dict[stage] if stage in value_dict else "",
classes=self.input_classes(),
disabled="disabled" if self.disabled else "",
)
for stage in self.gain_stages
)

View File

@ -22,7 +22,7 @@ class Q65ModeMatrix(Input):
id=self.checkbox_id(mode, interval),
checked="checked" if "{}{}".format(mode.name, interval.value) in value else "",
checkboxText="Mode {} interval {}s".format(mode.name, interval.value),
disabled="" if interval.is_available(mode) else "disabled",
disabled="" if interval.is_available(mode) and not self.disabled else "disabled",
)
def render_input(self, value):
@ -69,7 +69,7 @@ class WsjtDecodingDepthsInput(Input):
)
return """
<input type="hidden" class="{classes}" id="{id}" name="{id}" value="{value}">
<input type="hidden" class="{classes}" id="{id}" name="{id}" value="{value}" {disabled}>
<div class="inputs" style="display:none;">
<select class="form-control form-control-sm">{options}</select>
<input class="form-control form-control-sm" type="number" step="1">
@ -79,6 +79,7 @@ class WsjtDecodingDepthsInput(Input):
classes=self.input_classes(),
value=html.escape(value),
options="".join(render_mode(m) for m in Modes.getAvailableModes() if isinstance(m, WsjtMode)),
disabled="disabled" if self.disabled else ""
)
def input_classes(self):