/ src / bugreport / _internal / forms.py
forms.py
  1  from pathlib import Path
  2  from typing import cast
  3  import yaml
  4  from textual.app import App, ComposeResult
  5  from textual.widgets import Checkbox, Select, Input, Label, TextArea, Markdown
  6  
  7  from bugreport._internal.models.github import (
  8      CheckboxesAttributes,
  9      DropdownAttributes,
 10      FormElement,
 11      Form,
 12      InputAttributes,
 13      MarkdownAttributes,
 14      TextareaAttributes, Attributes,
 15      AttributesWithLabelDescription,
 16  )
 17  
 18  
 19  class FormApp(App):
 20      CSS_PATH = Path(__file__).parent / "forms.tcss"
 21      def __init__(self, **kwargs):
 22          super().__init__(**kwargs)
 23          self.current_form_data = {}
 24          self.current_form_index = 0
 25  
 26      # https://github.com/Textualize/textual/discussions/3968
 27      # https://textual.textualize.io/tutorial/#dynamic-widgets
 28      # https://textual.textualize.io/how-to/render-and-compose/
 29  
 30      def compose(self) -> ComposeResult:
 31          with open(".github/ISSUE_TEMPLATE/1-bug.yml") as file:
 32              form_data = yaml.safe_load(file)
 33          self.form = Form(**form_data)
 34          self.render_form(self.form.body[self.current_form_index])
 35  
 36      def render_form(self, element: FormElement):
 37          self.view.clear()
 38          yield from self.create_widgets(element)
 39          yield Button("Next", on_click=self.on_next_click)
 40  
 41      # def compose(self) -> ComposeResult:
 42      #     with open(".github/ISSUE_TEMPLATE/1-bug.yml") as file:
 43      #         form_data = yaml.safe_load(file)
 44      #     form = Form(**form_data)
 45      #     for element in form.body:
 46      #         yield from self.create_widgets(element)
 47  
 48      def create_label(self, attributes: AttributesWithLabelDescription):
 49          if attributes.label:
 50              yield Label(attributes.label)
 51  
 52      def create_description(self, attributes: AttributesWithLabelDescription):
 53          if attributes.description:
 54              yield Markdown(attributes.description)
 55  
 56      def create_textarea(self, attributes: TextareaAttributes):
 57          yield from self.create_label(attributes)
 58          yield from self.create_description(attributes)
 59          if attributes.render:
 60              yield TextArea.code_editor(
 61                  attributes.value or "",
 62                  language=attributes.render,
 63              )
 64          else:
 65              yield TextArea(attributes.value or "")
 66  
 67      def create_input(self, attributes: InputAttributes):
 68          yield from self.create_label(attributes)
 69          yield from self.create_description(attributes)
 70          yield Input(
 71              attributes.value,
 72              placeholder=attributes.placeholder or "",
 73          )
 74  
 75      def create_dropdown(self, attributes: DropdownAttributes):
 76          yield from self.create_label(attributes)
 77          yield from self.create_description(attributes)
 78          yield Select(
 79              [(option, option) for option in attributes.options],
 80              value=attributes.options[attributes.default] if attributes.default is not None else None,
 81              allow_blank=attributes.default is None,
 82              type_to_search=False,
 83          )
 84  
 85      def create_checkboxes(self, attributes: CheckboxesAttributes):
 86          yield from self.create_label(attributes)
 87          yield from self.create_description(attributes)
 88          for option in attributes.options:
 89              yield Checkbox(option.label, value=bool(option.required), disabled=bool(option.required))
 90  
 91      def create_markdown(self, attributes: MarkdownAttributes):
 92          yield Markdown(attributes.value)
 93  
 94      def create_widgets(self, element: FormElement):
 95          if element.type == "textarea":
 96              yield from self.create_textarea(element.attributes)  # type: ignore[arg-type]
 97          elif element.type == "input":
 98              yield from self.create_input(element.attributes)  # type: ignore[arg-type]
 99          elif element.type == "dropdown":
100              yield from self.create_dropdown(element.attributes)  # type: ignore[arg-type]
101          elif element.type == "checkboxes":
102              yield from self.create_checkboxes(element.attributes)  # type: ignore[arg-type]
103          elif element.type == "markdown":
104              yield from self.create_markdown(element.attributes)  # type: ignore[arg-type]
105  
106      async def on_next_click(self, event):
107          # Store current form data
108          for widget in self.view.children:
109              if isinstance(widget, Input):
110                  self.current_form_data[widget.id] = widget.value
111              elif isinstance(widget, TextArea):
112                  self.current_form_data[widget.id] = widget.value
113              elif isinstance(widget, Select):
114                  self.current_form_data[widget.id] = widget.value
115              elif isinstance(widget, Checkbox):
116                  self.current_form_data[widget.id] = widget.value
117  
118          # Move to the next form element
119          self.current_form_index += 1
120          if self.current_form_index < len(self.form.body):
121              self.render_form(self.form.body[self.current_form_index])
122          else:
123              # Handle form completion
124              self.on_form_complete()
125  
126      def on_form_complete(self):
127          self.view.clear()
128          yield Label("Form completed!")
129          yield Markdown(f"Collected data: {self.current_form_data}")
130  
131  if __name__ == "__main__":
132      FormApp().run()