initial commit
This commit is contained in:
parent
d02c23e9fb
commit
4a0dae531e
2348
pyscript-bootstrap-templates/HTML.py
Normal file
2348
pyscript-bootstrap-templates/HTML.py
Normal file
File diff suppressed because it is too large
Load Diff
1
pyscript-bootstrap-templates/__init__.py
Normal file
1
pyscript-bootstrap-templates/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.1.0"
|
1470
pyscript-bootstrap-templates/bootstrap_HTML.py
Normal file
1470
pyscript-bootstrap-templates/bootstrap_HTML.py
Normal file
File diff suppressed because it is too large
Load Diff
2300
pyscript-bootstrap-templates/bootstrap_HTML_container.py
Normal file
2300
pyscript-bootstrap-templates/bootstrap_HTML_container.py
Normal file
File diff suppressed because it is too large
Load Diff
646
pyscript-bootstrap-templates/bootstrap_inputs.py
Normal file
646
pyscript-bootstrap-templates/bootstrap_inputs.py
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
import datetime as dt
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from bootstrap_HTML import *
|
||||||
|
from js import document, FileReader, btoa # type: ignore
|
||||||
|
from pyodide import create_proxy # type: ignore
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
class InputLabel(HTML.Label, BootstrapContainer):
|
||||||
|
_default_class_name: str = "form-label"
|
||||||
|
|
||||||
|
|
||||||
|
class InputLabelCheckbox(InputLabel):
|
||||||
|
_default_class_name: str = "form-check-label"
|
||||||
|
|
||||||
|
|
||||||
|
class InputHelp(BootstrapContainer):
|
||||||
|
_default_class_name: str = "form-text"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControl(HTML.Input, BootstrapContainer):
|
||||||
|
_default_class_name: str = "form-control"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> str:
|
||||||
|
return self._parse_value()
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: str):
|
||||||
|
self._set_value(value)
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControlColor(InputFormControl):
|
||||||
|
_default_class_name: str = "form-control form-control-color"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControlNumber(InputFormControl):
|
||||||
|
_default_class_name: str = "form-control"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min(self):
|
||||||
|
return self.element.getAttribute("min")
|
||||||
|
|
||||||
|
@min.setter
|
||||||
|
def min(self, value):
|
||||||
|
self.element.setAttribute("min", str(value))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max(self):
|
||||||
|
return self.element.getAttribute("max")
|
||||||
|
|
||||||
|
@max.setter
|
||||||
|
def max(self, value):
|
||||||
|
self.element.setAttribute("max", str(value))
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControlRange(InputFormControl):
|
||||||
|
_default_class_name: str = "form-range"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControlSelect(InputFormControl):
|
||||||
|
_default_class_name: str = "form-select"
|
||||||
|
_tag_type: str = "select"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFormControlCheckbox(InputFormControl):
|
||||||
|
_default_class_name: str = "form-check-input"
|
||||||
|
|
||||||
|
|
||||||
|
class InputElement(BootstrapContainer):
|
||||||
|
|
||||||
|
_default_input_type = "text"
|
||||||
|
_default_input_class = InputFormControl
|
||||||
|
_default_label_class = InputLabel
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(parent=parent, id=id)
|
||||||
|
|
||||||
|
if floating_label and placeholder is None:
|
||||||
|
placeholder = " " # create a dummy placeholder
|
||||||
|
|
||||||
|
self._input = self.__class__._default_input_class(
|
||||||
|
parent=None,
|
||||||
|
id=self.id+"_input",
|
||||||
|
type=input_type if input_type is not None else self._default_input_type,
|
||||||
|
placeholder=placeholder
|
||||||
|
)
|
||||||
|
|
||||||
|
self._label = self.__class__._default_label_class(
|
||||||
|
parent=None, id=self.id+"_label", inner_html=label_text)
|
||||||
|
|
||||||
|
# if floating_label is True, add the input first, then the label
|
||||||
|
|
||||||
|
if floating_label:
|
||||||
|
self.append_child(self._input)
|
||||||
|
self.append_child(self._label)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.append_child(self._label)
|
||||||
|
self.append_child(self._input)
|
||||||
|
|
||||||
|
self._help_text = None
|
||||||
|
if help_text is not None:
|
||||||
|
self._help_text = InputHelp(
|
||||||
|
parent=self, id=self.id+"_help_text", inner_html=help_text)
|
||||||
|
|
||||||
|
self.floating_label = floating_label
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_small(self) -> bool:
|
||||||
|
return self._input.has_class("form-control-sm")
|
||||||
|
|
||||||
|
@is_small.setter
|
||||||
|
def is_small(self, value: bool):
|
||||||
|
self._input.set_class_name("form-control-sm", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_large(self) -> bool:
|
||||||
|
return self._input.has_class("form-control-lg")
|
||||||
|
|
||||||
|
@is_large.setter
|
||||||
|
def is_large(self, value: bool):
|
||||||
|
self._input.set_class_name("form-control-lg", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def readonly(self) -> bool:
|
||||||
|
return self._input.has_attribute("readonly")
|
||||||
|
|
||||||
|
@readonly.setter
|
||||||
|
def readonly(self, value: bool):
|
||||||
|
self._input.set_attribute("readonly", value, is_boolean_attribute=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._input.value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self._input.value = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def onchange(self):
|
||||||
|
return self._input.onchange
|
||||||
|
|
||||||
|
@onchange.setter
|
||||||
|
def onchange(self, value):
|
||||||
|
self._input.onchange = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def floating_label(self) -> bool:
|
||||||
|
return self.has_class("form-floating")
|
||||||
|
|
||||||
|
@floating_label.setter
|
||||||
|
def floating_label(self, value: bool):
|
||||||
|
self.set_class("form-floating", value)
|
||||||
|
|
||||||
|
|
||||||
|
class InputEmail(InputElement):
|
||||||
|
_default_input_type = "email"
|
||||||
|
|
||||||
|
|
||||||
|
class InputPassword(InputElement):
|
||||||
|
_default_input_type = "password"
|
||||||
|
|
||||||
|
|
||||||
|
class InputText(InputElement):
|
||||||
|
_default_input_type = "text"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFloat(InputElement):
|
||||||
|
_default_input_type = "number"
|
||||||
|
_default_number_class = float
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
min: _default_number_class = None,
|
||||||
|
max: _default_number_class = None,
|
||||||
|
step: _default_number_class = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
if min is not None:
|
||||||
|
self.min = min
|
||||||
|
|
||||||
|
if max is not None:
|
||||||
|
self.max = max
|
||||||
|
|
||||||
|
if step is not None:
|
||||||
|
self.step = step
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> _default_number_class:
|
||||||
|
val = self._input.value
|
||||||
|
if val == "":
|
||||||
|
return None
|
||||||
|
return self.__class__._default_number_class(self._input.value)
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: _default_number_class):
|
||||||
|
self._input.value = str(value) if value is not None else ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min(self) -> _default_number_class:
|
||||||
|
return self.__class__._default_number_class(self._input.get_attribute("min"))
|
||||||
|
|
||||||
|
@min.setter
|
||||||
|
def min(self, value: _default_number_class):
|
||||||
|
self._input.set_attribute("min", str(value))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max(self) -> _default_number_class:
|
||||||
|
return self.__class__._default_number_class(self._input.get_attribute("max"))
|
||||||
|
|
||||||
|
@max.setter
|
||||||
|
def max(self, value: _default_number_class):
|
||||||
|
self._input.set_attribute("max", str(value))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def step(self) -> _default_number_class:
|
||||||
|
return self.__class__._default_number_class(self._input.get_attribute("step"))
|
||||||
|
|
||||||
|
@step.setter
|
||||||
|
def step(self, value: _default_number_class):
|
||||||
|
self._input.set_attribute("step", str(value))
|
||||||
|
|
||||||
|
|
||||||
|
class InputRangeFloat(InputFloat):
|
||||||
|
_default_input_type = "range"
|
||||||
|
_default_input_class = InputFormControlRange
|
||||||
|
|
||||||
|
|
||||||
|
class InputInt(InputFloat):
|
||||||
|
|
||||||
|
_default_number_class = int
|
||||||
|
|
||||||
|
|
||||||
|
class InputRangeInt(InputInt):
|
||||||
|
_default_input_type = "range"
|
||||||
|
_default_input_class = InputFormControlRange
|
||||||
|
|
||||||
|
|
||||||
|
class InputDate(InputElement):
|
||||||
|
_default_input_type = "date"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> dt.datetime:
|
||||||
|
if self._input.value == "":
|
||||||
|
return None
|
||||||
|
return dt.datetime.strptime(self._input.value, "%Y-%m-%d").date()
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: Union[dt.datetime, dt.date, str, None]):
|
||||||
|
if isinstance(value, str):
|
||||||
|
self._input.value = value
|
||||||
|
self._input.value = value.strftime(
|
||||||
|
"%Y-%m-%d") if value is not None else ""
|
||||||
|
|
||||||
|
|
||||||
|
class InputTime(InputElement):
|
||||||
|
_default_input_type = "time"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> dt.datetime:
|
||||||
|
if self._input.value == "":
|
||||||
|
return None
|
||||||
|
return dt.datetime.strptime(self._input.value, "%H:%M").time()
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: Union[dt.datetime, dt.time, str, None]):
|
||||||
|
if isinstance(value, str):
|
||||||
|
self._input.value = value
|
||||||
|
self._input.value = value.strftime(
|
||||||
|
"%H:%M") if value is not None else ""
|
||||||
|
|
||||||
|
|
||||||
|
class InputRange(InputElement):
|
||||||
|
_default_input_type = "range"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFile(InputElement):
|
||||||
|
_default_input_type = "file"
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
self._files = {}
|
||||||
|
self._input.onchange = self._load_file
|
||||||
|
self._on_file_change = None
|
||||||
|
|
||||||
|
|
||||||
|
def _load_file(self, *args):
|
||||||
|
def read_file(event, name):
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
|
||||||
|
# FIXME: way too much decoding and encoding between formats here
|
||||||
|
# but bytearray conversation between js and python drove me crazy!!!
|
||||||
|
|
||||||
|
|
||||||
|
f = btoa(event.target.result)
|
||||||
|
buffer.write(base64.decodebytes(bytes(f, "utf-8")))
|
||||||
|
print(btoa(event.target.result))
|
||||||
|
self._files[name] = buffer
|
||||||
|
buffer.seek(0)
|
||||||
|
buffer.name = name # not standardized, but works here
|
||||||
|
if self._on_file_change is not None:
|
||||||
|
self._on_file_change(buffer)
|
||||||
|
|
||||||
|
fileList = self._input.element.files
|
||||||
|
|
||||||
|
self._files = {}
|
||||||
|
|
||||||
|
for f in fileList:
|
||||||
|
# reader is a pyodide.JsProxy
|
||||||
|
reader = FileReader.new()
|
||||||
|
|
||||||
|
# Create a Python proxy for the callback function
|
||||||
|
onload_event = create_proxy(lambda event, name=f.name: read_file(event, name))
|
||||||
|
|
||||||
|
reader.onload = onload_event
|
||||||
|
|
||||||
|
reader.readAsBinaryString(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._files
|
||||||
|
|
||||||
|
@property
|
||||||
|
def onchange(self):
|
||||||
|
return self._on_file_change
|
||||||
|
|
||||||
|
@onchange.setter
|
||||||
|
def onchange(self, value):
|
||||||
|
self._on_file_change = value
|
||||||
|
|
||||||
|
|
||||||
|
class InputMultiFile(InputFile):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
self._input.set_attribute("multiple", True, is_boolean_attribute=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
files = []
|
||||||
|
for i in range(self._input.element.files.length):
|
||||||
|
files.append(self._input.element.files.item(i).name)
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
class InputColor(InputElement):
|
||||||
|
_default_input_type = "color"
|
||||||
|
_default_input_class = InputFormControlColor
|
||||||
|
|
||||||
|
|
||||||
|
class InputDatalist(InputElement):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
options: List[str],
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
self._input.type = None
|
||||||
|
|
||||||
|
self._options = options
|
||||||
|
|
||||||
|
self._datalist = HTML.DataList(parent=self, id=self.id+"_datalist")
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
HTML.Option(inner_html=option, parent=self._datalist)
|
||||||
|
|
||||||
|
self._input.set_attribute("list", self._datalist.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
@options.setter
|
||||||
|
def options(self, value):
|
||||||
|
self._options = value
|
||||||
|
for c in self._datalist.children:
|
||||||
|
c.destroy()
|
||||||
|
for option in self._options:
|
||||||
|
HTML.Option(inner_html=option, parent=self._datalist)
|
||||||
|
|
||||||
|
|
||||||
|
class InputSelect(InputElement):
|
||||||
|
|
||||||
|
_default_input_class = InputFormControlSelect
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
options: List[str],
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
multiple: bool = False,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
self._input.type = None
|
||||||
|
|
||||||
|
self._options = options
|
||||||
|
|
||||||
|
self.multiple = multiple
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
HTML.Option(inner_html=option, parent=self._input)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
@options.setter
|
||||||
|
def options(self, value):
|
||||||
|
self._options = value
|
||||||
|
for c in self._input.children:
|
||||||
|
c.destroy()
|
||||||
|
for option in self._options:
|
||||||
|
HTML.Option(inner_html=option, parent=self._input)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def multiple(self):
|
||||||
|
return self._input.has_attribute("multiple")
|
||||||
|
|
||||||
|
@multiple.setter
|
||||||
|
def multiple(self, value):
|
||||||
|
self._input.set_attribute("multiple", value, is_boolean_attribute=True)
|
||||||
|
|
||||||
|
|
||||||
|
class InputCheckboxSingle(InputElement):
|
||||||
|
|
||||||
|
_default_class_name: str = "form-check"
|
||||||
|
_default_input_type = "checkbox"
|
||||||
|
_default_input_class = InputFormControlCheckbox
|
||||||
|
_default_label_class = InputLabelCheckbox
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
label_text: str = None,
|
||||||
|
help_text: str = None,
|
||||||
|
floating_label: bool = False,
|
||||||
|
placeholder: str = None,
|
||||||
|
name: str = None,
|
||||||
|
input_type=None,
|
||||||
|
id=None,
|
||||||
|
parent=None):
|
||||||
|
super().__init__(label_text=label_text,
|
||||||
|
help_text=help_text,
|
||||||
|
floating_label=floating_label,
|
||||||
|
placeholder=placeholder,
|
||||||
|
input_type=input_type,
|
||||||
|
id=id,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
self._input.set_attribute("name", name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def checked(self) -> bool:
|
||||||
|
return self._input.element.checked
|
||||||
|
|
||||||
|
@checked.setter
|
||||||
|
def checked(self, value: bool):
|
||||||
|
self._input.element.checked = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> bool:
|
||||||
|
return self.checked
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: bool):
|
||||||
|
self.checked = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inline(self) -> bool:
|
||||||
|
return self.has_class("inline")
|
||||||
|
|
||||||
|
@inline.setter
|
||||||
|
def inline(self, value: bool):
|
||||||
|
self.set_class("inline", value)
|
||||||
|
|
||||||
|
|
||||||
|
class InputRadioSingle(InputCheckboxSingle):
|
||||||
|
_default_input_type = "radio"
|
||||||
|
|
||||||
|
|
||||||
|
class InputSwitchSingle(InputCheckboxSingle):
|
||||||
|
_default_class_name: str = "form-check form-switch"
|
||||||
|
|
||||||
|
|
||||||
|
class InputCheckboxGroup(BootstrapContainer):
|
||||||
|
|
||||||
|
_default_input_class = InputCheckboxSingle
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
options: List[str],
|
||||||
|
inline: bool = False,
|
||||||
|
id: str = None,
|
||||||
|
label_text: Union[str, HTML.Element] = None,
|
||||||
|
group_name: str = None,
|
||||||
|
class_name: str = None,
|
||||||
|
parent: HTML.Element = None,
|
||||||
|
inner_html: str = None,) -> None:
|
||||||
|
super().__init__(inner_html=inner_html,
|
||||||
|
id=id,
|
||||||
|
class_name=class_name,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
if group_name is None:
|
||||||
|
group_name = "id-" + str(uuid.uuid4())
|
||||||
|
if label_text is not None:
|
||||||
|
if isinstance(label_text, str):
|
||||||
|
label_text = HTML.Label(inner_html=label_text, parent=self)
|
||||||
|
else:
|
||||||
|
self.append_child(label_text)
|
||||||
|
|
||||||
|
self._options = options
|
||||||
|
self._option_checkboxes = {}
|
||||||
|
self._group_name = group_name
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
cb = self.__class__._default_input_class(
|
||||||
|
option, name=group_name, parent=self)
|
||||||
|
cb.inline = inline
|
||||||
|
self._option_checkboxes[option] = cb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
@options.setter
|
||||||
|
def options(self, value):
|
||||||
|
self._options = value
|
||||||
|
for c in self._option_checkboxes.values():
|
||||||
|
c.destroy()
|
||||||
|
self._option_checkboxes = {}
|
||||||
|
for option in self._options:
|
||||||
|
cb = self.__class__._default_input_class(
|
||||||
|
option, name=self._group_name, parent=self)
|
||||||
|
self._option_checkboxes[option] = cb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
returns a list of checked options
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = []
|
||||||
|
for option in self._options:
|
||||||
|
if self._option_checkboxes[option].checked:
|
||||||
|
values.append(option)
|
||||||
|
return values
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value: List[str]):
|
||||||
|
for option in self._options:
|
||||||
|
self._option_checkboxes[option].checked = option in value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def option_checkboxes(self) -> Dict[str, InputCheckboxSingle]:
|
||||||
|
"""
|
||||||
|
returns the form elements for each option as dictionary
|
||||||
|
"""
|
||||||
|
return self._option_checkboxes
|
||||||
|
|
||||||
|
|
||||||
|
class InputRadioGroup(InputCheckboxGroup):
|
||||||
|
_default_input_class = InputRadioSingle
|
||||||
|
|
||||||
|
|
||||||
|
class InputSwitchGroup(InputCheckboxGroup):
|
||||||
|
_default_input_class = InputSwitchSingle
|
||||||
|
|
||||||
|
class Form(HTML.Form, BootstrapContainer):
|
||||||
|
pass
|
||||||
|
# TODO: implement more Form and form validation methods
|
90
pyscript-bootstrap-templates/bootstrap_templates.py
Normal file
90
pyscript-bootstrap-templates/bootstrap_templates.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import HTML
|
||||||
|
import bootstrap_HTML as bHTML
|
||||||
|
from js import document # type: ignore
|
||||||
|
|
||||||
|
class PyScriptBootstrapApp(object):
|
||||||
|
def __init__(self, parent_element:str = "pyscript_app"):
|
||||||
|
self._main_div = bHTML.ContainerFluid(id="main")
|
||||||
|
|
||||||
|
self.main_div.w = 100
|
||||||
|
self.main_div.h = 100
|
||||||
|
|
||||||
|
self._parent_element = document.getElementById(parent_element)
|
||||||
|
self._parent_element.appendChild(self.main_div.element)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def main_div(self) -> HTML.Div:
|
||||||
|
return self._main_div
|
||||||
|
|
||||||
|
class PyScriptBootstrapDashboard(PyScriptBootstrapApp):
|
||||||
|
def __init__(self, parent_element:str = "pyscript_app", brand_name = "Dashboard"):
|
||||||
|
|
||||||
|
super().__init__(parent_element)
|
||||||
|
|
||||||
|
row = bHTML.Row(parent=self.main_div)
|
||||||
|
row.height = "100%"
|
||||||
|
row.width = "100%"
|
||||||
|
row.mt = 5
|
||||||
|
row.display_property = bHTML.DisplayProperty.INLINE_FLEX
|
||||||
|
#row.set_attribute("style", "flex-shrink: 0;")
|
||||||
|
|
||||||
|
self._sidebar = bHTML.Col(id="sidebar", col_sm=5, col_md=4, col_lg=3, col_xl=3, parent=row)
|
||||||
|
|
||||||
|
self._navbar = bHTML.NavbarDark(
|
||||||
|
parent=self.main_div,
|
||||||
|
brand = brand_name,
|
||||||
|
nav_fill=True,
|
||||||
|
toggle_button_for_target=self.sidebar
|
||||||
|
)
|
||||||
|
|
||||||
|
self._navbar.position = bHTML.Position.FIXED_TOP
|
||||||
|
self._navbar.p = 2
|
||||||
|
|
||||||
|
|
||||||
|
row.append_child(self.sidebar)
|
||||||
|
self._sidebar.add_classes("sidebar")
|
||||||
|
self._sidebar.background_color = bHTML.BackgroundColor.LIGHT
|
||||||
|
self._sidebar.p = 4
|
||||||
|
self._sidebar.g = 2
|
||||||
|
self._sidebar.position = bHTML.Position.STATIC
|
||||||
|
self._sidebar.collapsable = True
|
||||||
|
self._sidebar.height = "100%"
|
||||||
|
self._sidebar.mw = 100
|
||||||
|
self._sidebar.shadow = bHTML.Shadow.LARGE
|
||||||
|
self._sidebar.overflow = bHTML.Overflow.SCROLL
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self._modal = bHTML.Modal(parent=self.main_div, title="Modal")
|
||||||
|
|
||||||
|
self._main_area = bHTML.Col(id="main_area", parent=row, col_lg=9, col_xl=9)
|
||||||
|
self._main_area.p = 4
|
||||||
|
self._main_area.overflow = bHTML.Overflow.SCROLL
|
||||||
|
self._main_area.height = "100%"
|
||||||
|
self._main_area.mw = 100
|
||||||
|
|
||||||
|
def show_modal(self):
|
||||||
|
self.modal.show()
|
||||||
|
|
||||||
|
def hide_modal(self):
|
||||||
|
self.modal.hide()
|
||||||
|
|
||||||
|
def toggle_modal(self):
|
||||||
|
self.modal.toggle()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def header(self) -> HTML.Header:
|
||||||
|
return self._header
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sidebar(self) -> HTML.Div:
|
||||||
|
return self._sidebar
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modal(self) -> HTML.Div:
|
||||||
|
return self._modal
|
||||||
|
|
||||||
|
@property
|
||||||
|
def main_area(self) -> HTML.Div:
|
||||||
|
return self._main_area
|
95
pyscript-bootstrap-templates/create.py
Normal file
95
pyscript-bootstrap-templates/create.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
|
||||||
|
import pathlib
|
||||||
|
from typing import List
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
# create a new pyscript project
|
||||||
|
|
||||||
|
def create_project(root_folder: pathlib.Path,
|
||||||
|
title: str,
|
||||||
|
packages: List[str],
|
||||||
|
paths: List[str] = None,
|
||||||
|
pyscript_css_url: str = "https://pyscript.net/alpha/pyscript.css",
|
||||||
|
pyscript_js_url: str = "https://pyscript.net/alpha/pyscript.js"):
|
||||||
|
|
||||||
|
pyenv = "- git+https://the-cake-is-a-lie.net/gogs/jonas/pyscript-bootstrap-templates.git"
|
||||||
|
for package in packages:
|
||||||
|
pyenv += f"\n - {package}"
|
||||||
|
|
||||||
|
if paths is not None:
|
||||||
|
pyenv += "\n - paths:"
|
||||||
|
for path in paths:
|
||||||
|
pyenv += f"\n - {path}"
|
||||||
|
|
||||||
|
pyenv += "\n"
|
||||||
|
|
||||||
|
html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="width: 100%;height: 100%">
|
||||||
|
<head id="head">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{pyscript_css_url}" />
|
||||||
|
<script defer src="{pyscript_js_url}"></script>
|
||||||
|
|
||||||
|
<title>{title}</title>
|
||||||
|
|
||||||
|
<py-env>
|
||||||
|
{pyenv}
|
||||||
|
</py-env>
|
||||||
|
</head>
|
||||||
|
<body style="width: 100%; height: 100%">
|
||||||
|
<div id="simple_app" style="height: 100%; min-height: 100%"></div>
|
||||||
|
<py-script src="./main.py"></py-script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
py = f"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not root_folder.exists():
|
||||||
|
root_folder.mkdir(parents=True)
|
||||||
|
|
||||||
|
(root_folder / "index.html").write_text(html, encoding="utf-8")
|
||||||
|
(root_folder / "main.py").write_text("", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
argument_parser = argparse.ArgumentParser(
|
||||||
|
description="create a new pyscript project")
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"root_folder", type=pathlib.Path, help="the root folder of the new project")
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"title", type=str, help="the title of the new project")
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"packages", type=str, nargs="+", help="the packages to include in the new project")
|
||||||
|
argument_parser.add_argument("--paths", type=str, nargs="+",
|
||||||
|
help="additional local python files to include in the new project")
|
||||||
|
argument_parser.add_argument("--pyscript_css_url", type=str,
|
||||||
|
help="the url of the pyscript css file", default="https://pyscript.net/alpha/pyscript.css")
|
||||||
|
argument_parser.add_argument("--pyscript_js_url", type=str,
|
||||||
|
help="the url of the pyscript js file", default="https://pyscript.net/alpha/pyscript.js")
|
||||||
|
arguments = argument_parser.parse_args()
|
||||||
|
|
||||||
|
create_project(arguments.root_folder,
|
||||||
|
arguments.title,
|
||||||
|
arguments.packages,
|
||||||
|
arguments.paths,
|
||||||
|
arguments.pyscript_css_url,
|
||||||
|
arguments.pyscript_js_url)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
exit(0)
|
56
setup.py
Normal file
56
setup.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
here = pathlib.Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def get_version(rel_path):
|
||||||
|
for line in (here / rel_path).read_text(encoding="utf-8").splitlines():
|
||||||
|
if line.startswith('__version__'):
|
||||||
|
delim = '"' if '"' in line else "'"
|
||||||
|
return line.split(delim)[1]
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unable to find version string.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Get the long description from the README file
|
||||||
|
long_description = (here / "README.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
|
||||||
|
name="pyscript-bootstrap-templates",
|
||||||
|
version=get_version("pyscript-bootstrap-templates/__init__.py"),
|
||||||
|
description="templates and basic python/pyscript wrappers for bootstrap 5",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
|
||||||
|
url="https://github.com/pypa/sampleproject",
|
||||||
|
|
||||||
|
author="Jonas Weinz",
|
||||||
|
|
||||||
|
author_email="author@example.com",
|
||||||
|
|
||||||
|
keywords="sample, setuptools, development",
|
||||||
|
|
||||||
|
package_dir={"": "pyscript-bootstrap-templates"},
|
||||||
|
packages=find_packages(where="pyscript-bootstrap-templates"),
|
||||||
|
|
||||||
|
|
||||||
|
python_requires=">=3.7, <4",
|
||||||
|
|
||||||
|
install_requires=[
|
||||||
|
"pillow",
|
||||||
|
"parse",
|
||||||
|
],
|
||||||
|
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"create_pyscript_bootstrap_app=create:main",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user