initial commit
This commit is contained in:
		
							
								
								
									
										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", | ||||
|         ], | ||||
|     }, | ||||
|  | ||||
| ) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user