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()