Browse Source
- Removed duplicate event handler definition in generator.py - Added complete feature summary documentation for BadGUI in BADGUI_COMPLETE_GUIDE.md - Introduced detailed workflow analysis in BADGUI_WORKFLOW_ANALYSIS.md - Created tests for debugging button click events in debug_button_test.py - Developed extended generic component tests in extended_generic_test.py - Implemented generic component tests showcasing bg.component() method in generic_component_test.py - Refactored components to utilize generic component() method in refactored_test.pymaster
9 changed files with 1535 additions and 115 deletions
@ -0,0 +1,329 @@ |
|||||||
|
# BadGUI Framework v0.2.0 - Complete Feature Summary |
||||||
|
|
||||||
|
## Overview |
||||||
|
BadGUI is a Python web UI framework that generates Vue.js applications with Quasar components. It provides a simple, declarative API similar to NiceGUI but generates static Vue.js projects that can be deployed anywhere. |
||||||
|
|
||||||
|
## Core Framework Features |
||||||
|
|
||||||
|
### 1. Component System |
||||||
|
BadGUI provides both predefined components and a generic component method: |
||||||
|
|
||||||
|
#### Predefined Components |
||||||
|
```python |
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
# Basic components |
||||||
|
bg.label("Hello World") |
||||||
|
bg.button("Click Me").on_click('handleClick') |
||||||
|
bg.input_("Enter text").bind_value('userText') |
||||||
|
|
||||||
|
# Layout components |
||||||
|
with bg.row(): |
||||||
|
with bg.column(): |
||||||
|
bg.label("Column content") |
||||||
|
|
||||||
|
# Card components (simplified syntax) |
||||||
|
with bg.card(): |
||||||
|
bg.label("Card content") |
||||||
|
bg.button("Action") |
||||||
|
``` |
||||||
|
|
||||||
|
#### Generic Component Method (NEW!) |
||||||
|
```python |
||||||
|
# Create ANY Quasar component |
||||||
|
rating = bg.component('q-rating', max=5, size='2em', color='amber') |
||||||
|
rating.bind_value('userRating') |
||||||
|
|
||||||
|
select = bg.component('q-select', |
||||||
|
filled=True, |
||||||
|
options=['Option 1', 'Option 2', 'Option 3'], |
||||||
|
label='Choose one') |
||||||
|
select.bind_value('selectedOption') |
||||||
|
|
||||||
|
slider = bg.component('q-slider', min=0, max=100, step=10, markers=True) |
||||||
|
slider.bind_value('volumeLevel') |
||||||
|
|
||||||
|
# Container components |
||||||
|
with bg.component('q-expansion-item', |
||||||
|
label='Advanced Settings', |
||||||
|
container=True) as expansion: |
||||||
|
bg.label("Nested content") |
||||||
|
bg.component('q-separator') |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. JavaScript Integration |
||||||
|
BadGUI provides comprehensive JavaScript integration with Vue.js: |
||||||
|
|
||||||
|
#### Script Component |
||||||
|
```python |
||||||
|
# Inline JavaScript |
||||||
|
bg.script(""" |
||||||
|
const userRating = ref(3); |
||||||
|
const selectedOption = ref(null); |
||||||
|
|
||||||
|
const updateDisplay = () => { |
||||||
|
console.log('Rating:', userRating.value); |
||||||
|
}; |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
console.log('Component mounted!'); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
# External JavaScript files |
||||||
|
bg.script("./my-script.js") |
||||||
|
|
||||||
|
# ES6 Module imports (automatically copied to Vue app) |
||||||
|
bg.script("./utils/helpers.js", import_mode=True) |
||||||
|
``` |
||||||
|
|
||||||
|
#### Vue Ref Integration |
||||||
|
```python |
||||||
|
# Create components with Vue template refs |
||||||
|
button = bg.button("Click Me") |
||||||
|
button.vue_ref('myButton') |
||||||
|
|
||||||
|
# Access in JavaScript |
||||||
|
bg.script(""" |
||||||
|
const { myButton } = getCurrentInstance().refs; |
||||||
|
|
||||||
|
const focusButton = () => { |
||||||
|
myButton.value.$el.focus(); |
||||||
|
}; |
||||||
|
""") |
||||||
|
``` |
||||||
|
|
||||||
|
### 3. Data Binding |
||||||
|
BadGUI supports comprehensive Vue.js data binding: |
||||||
|
|
||||||
|
```python |
||||||
|
# Two-way binding |
||||||
|
input_field = bg.input_("Enter text") |
||||||
|
input_field.bind_value('userInput') |
||||||
|
|
||||||
|
# Text binding |
||||||
|
display = bg.label("") |
||||||
|
display.bind_text('computedText') |
||||||
|
|
||||||
|
# Event handling with .on() method pattern |
||||||
|
button = bg.button("Submit") |
||||||
|
button.on_click('submitForm') |
||||||
|
button.on('mouseenter', 'handleHover') |
||||||
|
``` |
||||||
|
|
||||||
|
### 4. Context Management |
||||||
|
BadGUI uses Python context managers for clean layout structure: |
||||||
|
|
||||||
|
```python |
||||||
|
with bg.page("/", "HomePage", "My App"): |
||||||
|
with bg.column().classes("q-pa-lg"): |
||||||
|
bg.label("Page Title").classes("text-h3") |
||||||
|
|
||||||
|
with bg.row().classes("q-gutter-md"): |
||||||
|
with bg.card(): |
||||||
|
bg.label("Card 1") |
||||||
|
|
||||||
|
with bg.card(): |
||||||
|
bg.label("Card 2") |
||||||
|
|
||||||
|
# Generic container components |
||||||
|
with bg.component('q-timeline', container=True): |
||||||
|
bg.component('q-timeline-entry', |
||||||
|
title='Event 1', |
||||||
|
subtitle='Description') |
||||||
|
``` |
||||||
|
|
||||||
|
## Complete API Reference |
||||||
|
|
||||||
|
### App Class Methods |
||||||
|
```python |
||||||
|
# Page management |
||||||
|
bg.page(route, name, title) |
||||||
|
|
||||||
|
# Layout components |
||||||
|
bg.row(**kwargs) |
||||||
|
bg.column(**kwargs) |
||||||
|
bg.div(**kwargs) |
||||||
|
|
||||||
|
# Content components |
||||||
|
bg.label(text, **kwargs) |
||||||
|
bg.button(text, **kwargs) |
||||||
|
bg.input_(placeholder, **kwargs) |
||||||
|
bg.card(**kwargs) |
||||||
|
|
||||||
|
# Generic component creation |
||||||
|
bg.component(quasar_component, container=False, **kwargs) |
||||||
|
|
||||||
|
# JavaScript integration |
||||||
|
bg.script(code_or_file, import_mode=False) |
||||||
|
|
||||||
|
# Development server |
||||||
|
bg.dev(port=3000, host='localhost') |
||||||
|
``` |
||||||
|
|
||||||
|
### Component Methods |
||||||
|
```python |
||||||
|
# Styling |
||||||
|
component.classes("q-pa-md text-h5") |
||||||
|
component.style("color: red; font-size: 16px") |
||||||
|
|
||||||
|
# Data binding |
||||||
|
component.bind_value('variable_name') |
||||||
|
component.bind_text('text_variable') |
||||||
|
|
||||||
|
# Vue refs |
||||||
|
component.vue_ref('ref_name') |
||||||
|
|
||||||
|
# Event handling (.on() pattern) |
||||||
|
component.on_click('function_name') |
||||||
|
component.on('event_name', 'handler_function') |
||||||
|
|
||||||
|
# Properties |
||||||
|
component.props(disabled=True, color='primary') |
||||||
|
``` |
||||||
|
|
||||||
|
## Example Applications |
||||||
|
|
||||||
|
### 1. Simple Form with Generic Components |
||||||
|
```python |
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "Form", "Advanced Form"): |
||||||
|
with bg.column().classes("q-pa-lg q-gutter-md"): |
||||||
|
bg.label("User Preferences").classes("text-h4") |
||||||
|
|
||||||
|
# Rating |
||||||
|
bg.label("Rate our service:") |
||||||
|
rating = bg.component('q-rating', max=5, size='2em') |
||||||
|
rating.bind_value('serviceRating') |
||||||
|
|
||||||
|
# Select |
||||||
|
category = bg.component('q-select', |
||||||
|
options=['General', 'Technical', 'Billing'], |
||||||
|
label='Category') |
||||||
|
category.bind_value('selectedCategory') |
||||||
|
|
||||||
|
# Slider |
||||||
|
bg.label("Priority Level:") |
||||||
|
priority = bg.component('q-slider', min=1, max=10, step=1) |
||||||
|
priority.bind_value('priorityLevel') |
||||||
|
|
||||||
|
# Toggle |
||||||
|
notifications = bg.component('q-toggle', |
||||||
|
label='Email notifications') |
||||||
|
notifications.bind_value('emailEnabled') |
||||||
|
|
||||||
|
# Submit button |
||||||
|
bg.button("Submit Feedback").on_click('submitFeedback') |
||||||
|
|
||||||
|
# JavaScript handling |
||||||
|
bg.script(""" |
||||||
|
const serviceRating = ref(5); |
||||||
|
const selectedCategory = ref('General'); |
||||||
|
const priorityLevel = ref(5); |
||||||
|
const emailEnabled = ref(true); |
||||||
|
|
||||||
|
const submitFeedback = () => { |
||||||
|
const feedback = { |
||||||
|
rating: serviceRating.value, |
||||||
|
category: selectedCategory.value, |
||||||
|
priority: priorityLevel.value, |
||||||
|
notifications: emailEnabled.value |
||||||
|
}; |
||||||
|
|
||||||
|
console.log('Feedback submitted:', feedback); |
||||||
|
// Send to API... |
||||||
|
}; |
||||||
|
""") |
||||||
|
|
||||||
|
bg.dev(port=3030) |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. Dashboard with Container Components |
||||||
|
```python |
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "Dashboard", "Analytics Dashboard"): |
||||||
|
with bg.column().classes("full-width"): |
||||||
|
bg.label("Analytics Dashboard").classes("text-h3 q-pa-lg") |
||||||
|
|
||||||
|
with bg.row().classes("q-pa-md q-gutter-md"): |
||||||
|
# Stats cards |
||||||
|
with bg.card().classes("col"): |
||||||
|
bg.label("Total Users").classes("text-h6") |
||||||
|
user_count = bg.label("0") |
||||||
|
user_count.bind_text('totalUsers') |
||||||
|
|
||||||
|
with bg.card().classes("col"): |
||||||
|
bg.label("Revenue").classes("text-h6") |
||||||
|
revenue = bg.label("$0") |
||||||
|
revenue.bind_text('totalRevenue') |
||||||
|
|
||||||
|
# Expandable sections |
||||||
|
with bg.component('q-expansion-item', |
||||||
|
label='Detailed Analytics', |
||||||
|
icon='analytics', |
||||||
|
container=True): |
||||||
|
|
||||||
|
# Progress indicators |
||||||
|
bg.label("Loading Progress:") |
||||||
|
bg.component('q-linear-progress', |
||||||
|
value=0.8, |
||||||
|
size='15px', |
||||||
|
color='primary') |
||||||
|
|
||||||
|
# Data table (could be implemented as generic component) |
||||||
|
with bg.component('q-table', |
||||||
|
title='Recent Activity', |
||||||
|
columns=[{'name': 'date', 'label': 'Date'}], |
||||||
|
container=True): |
||||||
|
pass # Table rows would be added here |
||||||
|
|
||||||
|
bg.script(""" |
||||||
|
const totalUsers = ref('1,234'); |
||||||
|
const totalRevenue = ref('$56,789'); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
// Simulate data loading |
||||||
|
console.log('Dashboard loaded'); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
bg.dev(port=3031) |
||||||
|
``` |
||||||
|
|
||||||
|
## Framework Architecture |
||||||
|
|
||||||
|
### Generated Vue.js Structure |
||||||
|
``` |
||||||
|
vue-app/ |
||||||
|
├── src/ |
||||||
|
│ ├── components/ |
||||||
|
│ │ └── BadGUIComponents.vue # Generated components |
||||||
|
│ ├── pages/ |
||||||
|
│ │ └── IndexPage.vue # Generated pages |
||||||
|
│ ├── utils/ # ES6 modules (if using import_mode) |
||||||
|
│ │ └── helpers.js |
||||||
|
│ ├── App.vue |
||||||
|
│ └── main.js |
||||||
|
├── package.json |
||||||
|
└── quasar.config.js |
||||||
|
``` |
||||||
|
|
||||||
|
### Key Benefits |
||||||
|
|
||||||
|
1. **Extensibility**: Generic `component()` method allows access to entire Quasar library |
||||||
|
2. **Vue.js Integration**: Full Vue 3 Composition API support with refs, reactive data, and lifecycle hooks |
||||||
|
3. **JavaScript Flexibility**: Inline scripts, external files, and ES6 module imports |
||||||
|
4. **Clean Syntax**: Context managers and method chaining for readable code |
||||||
|
5. **Static Generation**: Produces deployable Vue.js applications |
||||||
|
6. **Type Safety**: Python-based development with IDE support |
||||||
|
|
||||||
|
## Development Workflow |
||||||
|
|
||||||
|
1. **Write BadGUI Code**: Use Python to define UI structure and behavior |
||||||
|
2. **Test with Dev Server**: `bg.dev()` automatically generates and serves Vue app |
||||||
|
3. **Iterate Quickly**: Changes to Python code regenerate the Vue app |
||||||
|
4. **Deploy**: Generated Vue app can be built and deployed anywhere |
||||||
|
|
||||||
|
BadGUI v0.2.0 combines the simplicity of Python-based UI frameworks with the power and flexibility of Vue.js and Quasar, making it perfect for rapid prototyping and production applications alike. |
||||||
@ -0,0 +1,678 @@ |
|||||||
|
# BadGUI Workflow Analysis: From Python to Vue.js |
||||||
|
|
||||||
|
## Overview |
||||||
|
|
||||||
|
BadGUI is a Python framework that generates complete Vue.js/Quasar projects using a NiceGUI-like syntax. This document traces the complete workflow from Python code to a running Vue.js application. |
||||||
|
|
||||||
|
## Table of Contents |
||||||
|
|
||||||
|
1. [Python Code Structure](#1-python-code-structure) |
||||||
|
2. [Component Creation and Storage](#2-component-creation-and-storage) |
||||||
|
3. [Page Management](#3-page-management) |
||||||
|
4. [Build Process](#4-build-process) |
||||||
|
5. [Vue.js Generation](#5-vuejs-generation) |
||||||
|
6. [Project Structure Creation](#6-project-structure-creation) |
||||||
|
7. [Development Server](#7-development-server) |
||||||
|
8. [Areas for Improvement](#8-areas-for-improvement) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 1. Python Code Structure |
||||||
|
|
||||||
|
### Example Input Code |
||||||
|
|
||||||
|
```python |
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "HomePage", "Home"): |
||||||
|
with bg.column() as main_col: |
||||||
|
main_col.classes("q-pa-lg q-gutter-md") |
||||||
|
bg.label("Hello BadGUI!").classes("text-h3") |
||||||
|
|
||||||
|
name_input = bg.input("Enter your name") |
||||||
|
name_input.bind_value("userName") |
||||||
|
|
||||||
|
bg.button("Greet").on_click("greetUser").classes("q-btn-primary") |
||||||
|
|
||||||
|
bg.script(""" |
||||||
|
const userName = ref(''); |
||||||
|
const greetUser = () => { |
||||||
|
$q.notify(`Hello, ${userName.value}!`); |
||||||
|
}; |
||||||
|
""") |
||||||
|
|
||||||
|
bg.dev(port=3000) |
||||||
|
``` |
||||||
|
|
||||||
|
### Key Framework Entry Points |
||||||
|
|
||||||
|
**File: `badgui/__init__.py`** |
||||||
|
```python |
||||||
|
from .core import App |
||||||
|
|
||||||
|
# Main app instance |
||||||
|
app = App() |
||||||
|
|
||||||
|
# Convenience functions that delegate to the main app |
||||||
|
def label(text: str, **kwargs): |
||||||
|
return app.label(text, **kwargs) |
||||||
|
|
||||||
|
def button(text: str, icon: str = None, color: str = 'primary', **kwargs): |
||||||
|
return app.button(text, icon, color, **kwargs) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** All component functions are convenience wrappers around a singleton `App` instance. This centralizes state management but could limit flexibility for multiple app instances. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 2. Component Creation and Storage |
||||||
|
|
||||||
|
### Component Class Structure |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
class Component: |
||||||
|
def __init__(self, component_type: str, **props): |
||||||
|
self.component_type = component_type |
||||||
|
self.id = f"{component_type}_{id(self)}" |
||||||
|
self._props = props |
||||||
|
self._classes = [] |
||||||
|
self._style = {} |
||||||
|
self.children = [] |
||||||
|
self._vue_ref = None |
||||||
|
|
||||||
|
def classes(self, class_names: Union[str, List[str]]): |
||||||
|
if isinstance(class_names, str): |
||||||
|
self._classes = class_names.split() |
||||||
|
else: |
||||||
|
self._classes = class_names |
||||||
|
return self |
||||||
|
|
||||||
|
def vue_ref(self, ref_name: str): |
||||||
|
self._vue_ref = ref_name |
||||||
|
self._props['ref'] = ref_name |
||||||
|
return self |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Components use method chaining for fluent API. Each component gets a unique ID based on memory address. Props, classes, and styles are stored separately but could be unified. |
||||||
|
|
||||||
|
### Component Addition Process |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
class App: |
||||||
|
def _add_component(self, component: Component) -> Component: |
||||||
|
page = self._get_current_page() |
||||||
|
|
||||||
|
# Add to current container (if in context) or page |
||||||
|
if page.current_container: |
||||||
|
page.current_container.children.append(component) |
||||||
|
else: |
||||||
|
page.components.append(component) |
||||||
|
|
||||||
|
return component |
||||||
|
|
||||||
|
def label(self, text: str, **kwargs) -> Component: |
||||||
|
component = Component('label', text=text, **kwargs) |
||||||
|
return self._add_component(component) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Components are added to either the current page or the current container (for nested structures). The container stack manages nesting levels. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 3. Page Management |
||||||
|
|
||||||
|
### Page Class and Context Management |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
class Page: |
||||||
|
def __init__(self, path: str, name: str, title: str = None): |
||||||
|
self.path = path |
||||||
|
self.name = name |
||||||
|
self.title = title or name |
||||||
|
self.components = [] |
||||||
|
self.current_container = None |
||||||
|
self._container_stack = [] |
||||||
|
self._scripts = [] |
||||||
|
|
||||||
|
class App: |
||||||
|
def __init__(self): |
||||||
|
self.pages = {} |
||||||
|
self.current_page = None |
||||||
|
|
||||||
|
@contextmanager |
||||||
|
def page(self, path: str, name: str = None, title: str = None): |
||||||
|
# Create or get existing page |
||||||
|
page = Page(path, name or self._generate_page_name(path), title) |
||||||
|
self.pages[path] = page |
||||||
|
|
||||||
|
# Set as current page |
||||||
|
old_page = self.current_page |
||||||
|
self.current_page = page |
||||||
|
|
||||||
|
try: |
||||||
|
yield page |
||||||
|
finally: |
||||||
|
self.current_page = old_page |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Pages use context managers to maintain state. The current page is tracked globally, which could cause issues with concurrent page creation. |
||||||
|
|
||||||
|
### Container Context Management |
||||||
|
|
||||||
|
```python |
||||||
|
@contextmanager |
||||||
|
def column(self, **kwargs): |
||||||
|
component = Component('column', classes=['column'], **kwargs) |
||||||
|
component = self._add_component(component) |
||||||
|
|
||||||
|
# Enter container context |
||||||
|
page = self._get_current_page() |
||||||
|
page._container_stack.append(page.current_container) |
||||||
|
page.current_container = component |
||||||
|
|
||||||
|
try: |
||||||
|
yield component |
||||||
|
finally: |
||||||
|
# Exit container context |
||||||
|
page.current_container = page._container_stack.pop() |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Container nesting uses a stack-based approach. This works well but could be error-prone if exceptions occur during context management. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 4. Build Process |
||||||
|
|
||||||
|
### Build Initiation |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
def build(self, output_dir: str = "./badgui-output", project_name: str = "badgui-app", **kwargs): |
||||||
|
from .generator import VueGenerator |
||||||
|
|
||||||
|
print(f"🔧 Building BadGUI project...") |
||||||
|
print(f"📁 Output directory: {output_dir}") |
||||||
|
|
||||||
|
generator = VueGenerator(self) |
||||||
|
generator.generate_project(output_dir, project_name, **kwargs) |
||||||
|
|
||||||
|
print("✅ BadGUI project generated successfully!") |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Build process is straightforward but lacks error handling and progress reporting for large projects. |
||||||
|
|
||||||
|
### Generator Initialization |
||||||
|
|
||||||
|
**File: `badgui/generator.py`** |
||||||
|
```python |
||||||
|
class VueGenerator: |
||||||
|
def __init__(self, app: App): |
||||||
|
self.app = app |
||||||
|
|
||||||
|
def generate_project(self, output_dir: str, project_name: str = "badgui-app", **kwargs): |
||||||
|
os.makedirs(output_dir, exist_ok=True) |
||||||
|
|
||||||
|
self._create_project_structure(output_dir, project_name) |
||||||
|
self._generate_package_json(output_dir, project_name) |
||||||
|
self._generate_quasar_config(output_dir) |
||||||
|
self._generate_main_layout(output_dir) |
||||||
|
self._generate_pages(output_dir) |
||||||
|
self._generate_router(output_dir) |
||||||
|
self._generate_app_vue(output_dir) |
||||||
|
self._generate_boot_files(output_dir) |
||||||
|
self._generate_css_files(output_dir) |
||||||
|
self._copy_static_files(output_dir) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Generation follows a fixed sequence. Each step is independent, which is good for maintainability but doesn't allow for conditional generation based on features used. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 5. Vue.js Generation |
||||||
|
|
||||||
|
### Component-to-Vue Template Conversion |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
def to_vue_template(self, indent: int = 0) -> str: |
||||||
|
spaces = " " * indent |
||||||
|
|
||||||
|
# Build attributes |
||||||
|
attrs = [] |
||||||
|
attrs.append(f'id="{self.id}"') |
||||||
|
|
||||||
|
if self._classes: |
||||||
|
attrs.append(f'class="{" ".join(self._classes)}"') |
||||||
|
|
||||||
|
# Add other props |
||||||
|
for key, value in self._props.items(): |
||||||
|
if key not in ['classes', 'style', 'id']: |
||||||
|
if isinstance(value, bool) and value: |
||||||
|
attrs.append(key) |
||||||
|
elif isinstance(value, str): |
||||||
|
attrs.append(f'{key}="{value}"') |
||||||
|
|
||||||
|
attrs_str = " " + " ".join(attrs) |
||||||
|
vue_tag = self.get_vue_tag() |
||||||
|
|
||||||
|
# Generate template |
||||||
|
if self.children: |
||||||
|
template = f"{spaces}<{vue_tag}{attrs_str}>\n" |
||||||
|
for child in self.children: |
||||||
|
template += child.to_vue_template(indent + 1) |
||||||
|
template += f"{spaces}</{vue_tag}>\n" |
||||||
|
else: |
||||||
|
content = self.get_content() |
||||||
|
if content: |
||||||
|
template = f"{spaces}<{vue_tag}{attrs_str}>{content}</{vue_tag}>\n" |
||||||
|
else: |
||||||
|
template = f"{spaces}<{vue_tag}{attrs_str} />\n" |
||||||
|
|
||||||
|
return template |
||||||
|
|
||||||
|
def get_vue_tag(self) -> str: |
||||||
|
component_map = { |
||||||
|
'label': 'q-item-label', |
||||||
|
'button': 'q-btn', |
||||||
|
'input': 'q-input', |
||||||
|
'column': 'div', |
||||||
|
'card': 'q-card', |
||||||
|
'dialog': 'q-dialog', |
||||||
|
'div': 'div' |
||||||
|
} |
||||||
|
return component_map.get(self.component_type, self.component_type) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Template generation is recursive and handles nested components well. The component mapping is hardcoded, which makes adding new components require code changes. |
||||||
|
|
||||||
|
### Page Component Generation |
||||||
|
|
||||||
|
**File: `badgui/generator.py`** |
||||||
|
```python |
||||||
|
def _generate_page_component(self, output_dir: str, page): |
||||||
|
template_content = self._generate_vue_template_for_page(page) |
||||||
|
script_imports, script_content = self._process_page_scripts(page, output_dir) |
||||||
|
|
||||||
|
page_content = f'''<template> |
||||||
|
<q-page class="row items-center justify-evenly"> |
||||||
|
{template_content} |
||||||
|
</q-page> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import {{ ref, onMounted, watch }} from 'vue' |
||||||
|
import {{ useQuasar }} from 'quasar' |
||||||
|
{script_imports} |
||||||
|
|
||||||
|
// Component state |
||||||
|
const state = ref({{}}) |
||||||
|
const $q = useQuasar() |
||||||
|
|
||||||
|
// Dialog state for Quasar dialogs |
||||||
|
const dialogOpen = ref(false) |
||||||
|
|
||||||
|
// Make window object available to template |
||||||
|
const window = globalThis.window |
||||||
|
|
||||||
|
{script_content} |
||||||
|
</script>''' |
||||||
|
|
||||||
|
page_path = os.path.join(output_dir, 'src', 'pages', f'{page.name}.vue') |
||||||
|
with open(page_path, 'w') as f: |
||||||
|
f.write(page_content) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Page generation is template-based with string formatting. This approach works but is fragile and hard to modify. A proper template engine would be more robust. |
||||||
|
|
||||||
|
### Script Processing |
||||||
|
|
||||||
|
```python |
||||||
|
def _process_page_scripts(self, page, output_dir: str) -> tuple: |
||||||
|
script_imports = "" |
||||||
|
script_content = "" |
||||||
|
|
||||||
|
# Collect Vue refs |
||||||
|
vue_refs = self._collect_vue_refs(page) |
||||||
|
if vue_refs: |
||||||
|
for ref_name in vue_refs: |
||||||
|
script_content += f"\n// Vue ref for component\nconst {ref_name} = ref(null);\n" |
||||||
|
|
||||||
|
# Process script components |
||||||
|
if hasattr(page, '_scripts'): |
||||||
|
for script_component in page._scripts: |
||||||
|
props = script_component._props |
||||||
|
|
||||||
|
if props.get('_is_inline'): |
||||||
|
content = props.get('_script_content', '') |
||||||
|
script_content += f"\n// Inline script\n{content}\n" |
||||||
|
|
||||||
|
elif props.get('_external_file'): |
||||||
|
filename = props.get('_external_file') |
||||||
|
import_mode = props.get('_import_mode', False) |
||||||
|
|
||||||
|
if import_mode: |
||||||
|
# ES6 module import |
||||||
|
utils_dir = os.path.join(output_dir, 'src', 'utils') |
||||||
|
os.makedirs(utils_dir, exist_ok=True) |
||||||
|
|
||||||
|
module_name = os.path.splitext(filename)[0] |
||||||
|
script_imports += f"import * as {module_name} from '../utils/{filename}';\n" |
||||||
|
else: |
||||||
|
# Traditional script tag |
||||||
|
js_dir = os.path.join(output_dir, 'public', 'js') |
||||||
|
os.makedirs(js_dir, exist_ok=True) |
||||||
|
|
||||||
|
return script_imports, script_content |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Script processing handles both inline and external scripts with ES6 module support. The dual approach (traditional vs module imports) adds complexity but provides flexibility. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 6. Project Structure Creation |
||||||
|
|
||||||
|
### Package.json Generation |
||||||
|
|
||||||
|
**File: `badgui/generator.py`** |
||||||
|
```python |
||||||
|
def _generate_package_json(self, output_dir: str, project_name: str): |
||||||
|
package_json = { |
||||||
|
"name": project_name, |
||||||
|
"version": "0.0.1", |
||||||
|
"scripts": { |
||||||
|
"dev": "quasar dev", |
||||||
|
"build": "quasar build" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@quasar/extras": "^1.16.4", |
||||||
|
"quasar": "^2.12.0", |
||||||
|
"vue": "^3.3.4", |
||||||
|
"vue-router": "^4.2.4" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@quasar/app-vite": "^1.3.0", |
||||||
|
"typescript": "^5.1.6" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
with open(os.path.join(output_dir, 'package.json'), 'w') as f: |
||||||
|
json.dump(package_json, f, indent=2) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Dependencies are hardcoded with specific versions. This ensures consistency but makes updates difficult and doesn't allow for feature-based dependency inclusion. |
||||||
|
|
||||||
|
### Router Generation |
||||||
|
|
||||||
|
```python |
||||||
|
def _generate_router(self, output_dir: str): |
||||||
|
page_routes = [] |
||||||
|
for path, page in self.app.pages.items(): |
||||||
|
route = f" {{ path: '{path}', component: () => import('pages/{page.name}.vue') }}" |
||||||
|
page_routes.append(route) |
||||||
|
|
||||||
|
routes_content = f'''const routes = [ |
||||||
|
{{ |
||||||
|
path: '/', |
||||||
|
component: () => import('layouts/MainLayout.vue'), |
||||||
|
children: [ |
||||||
|
{",".join([chr(10) + route for route in page_routes])} |
||||||
|
] |
||||||
|
}} |
||||||
|
] |
||||||
|
|
||||||
|
export default routes''' |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Router generation is dynamic based on defined pages. This works well for basic routing but doesn't support advanced features like route guards or nested routing. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 7. Development Server |
||||||
|
|
||||||
|
### Dev Server Implementation |
||||||
|
|
||||||
|
**File: `badgui/core.py`** |
||||||
|
```python |
||||||
|
def dev(self, port: int = 9000, host: str = "localhost", auto_reload: bool = False, |
||||||
|
project_name: str = "badgui-dev", **kwargs): |
||||||
|
import tempfile |
||||||
|
import subprocess |
||||||
|
|
||||||
|
# Create temporary build |
||||||
|
temp_dir = tempfile.mkdtemp(prefix="badgui_dev_") |
||||||
|
print(f"🔧 Creating temporary build...") |
||||||
|
print(f"📁 Temp directory: {temp_dir}") |
||||||
|
|
||||||
|
self.build(temp_dir, project_name, **kwargs) |
||||||
|
|
||||||
|
# Install dependencies and run dev server |
||||||
|
print("📦 Installing dependencies...") |
||||||
|
subprocess.run(["npm", "install"], cwd=temp_dir, check=True, capture_output=True) |
||||||
|
|
||||||
|
print(f"🚀 Starting dev server at http://{host}:{port}") |
||||||
|
env = os.environ.copy() |
||||||
|
env['PORT'] = str(port) |
||||||
|
env['HOST'] = host |
||||||
|
|
||||||
|
try: |
||||||
|
subprocess.run(["npm", "run", "dev"], cwd=temp_dir, env=env, check=True) |
||||||
|
except KeyboardInterrupt: |
||||||
|
print("\n🛑 Dev server stopped") |
||||||
|
finally: |
||||||
|
# Cleanup temp directory |
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True) |
||||||
|
``` |
||||||
|
|
||||||
|
**Analysis:** Dev server creates temporary builds, which is inefficient for development. A proper dev server would support hot reloading and incremental builds. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 8. Areas for Improvement |
||||||
|
|
||||||
|
### 8.1 Architecture Improvements |
||||||
|
|
||||||
|
#### Issue: Singleton App Instance |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
# badgui/__init__.py |
||||||
|
app = App() # Global singleton |
||||||
|
|
||||||
|
def label(text: str, **kwargs): |
||||||
|
return app.label(text, **kwargs) |
||||||
|
``` |
||||||
|
|
||||||
|
**Problem:** Single global instance limits flexibility and testing. |
||||||
|
|
||||||
|
**Improvement:** Support multiple app instances: |
||||||
|
```python |
||||||
|
class BadGUI: |
||||||
|
def __init__(self): |
||||||
|
self.app = App() |
||||||
|
|
||||||
|
def label(self, text: str, **kwargs): |
||||||
|
return self.app.label(text, **kwargs) |
||||||
|
|
||||||
|
# Default instance for convenience |
||||||
|
bg = BadGUI() |
||||||
|
label = bg.label |
||||||
|
``` |
||||||
|
|
||||||
|
#### Issue: Component Mapping Hardcoded |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
def get_vue_tag(self) -> str: |
||||||
|
component_map = { |
||||||
|
'label': 'q-item-label', |
||||||
|
'button': 'q-btn', |
||||||
|
# ... hardcoded mapping |
||||||
|
} |
||||||
|
return component_map.get(self.component_type, self.component_type) |
||||||
|
``` |
||||||
|
|
||||||
|
**Improvement:** Plugin-based component system: |
||||||
|
```python |
||||||
|
class ComponentRegistry: |
||||||
|
def __init__(self): |
||||||
|
self._components = {} |
||||||
|
|
||||||
|
def register(self, component_type: str, vue_tag: str, props_mapper=None): |
||||||
|
self._components[component_type] = { |
||||||
|
'vue_tag': vue_tag, |
||||||
|
'props_mapper': props_mapper |
||||||
|
} |
||||||
|
|
||||||
|
def get_vue_tag(self, component_type: str) -> str: |
||||||
|
return self._components.get(component_type, {}).get('vue_tag', component_type) |
||||||
|
``` |
||||||
|
|
||||||
|
### 8.2 Performance Improvements |
||||||
|
|
||||||
|
#### Issue: Inefficient Dev Server |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
def dev(self, port: int = 9000, **kwargs): |
||||||
|
temp_dir = tempfile.mkdtemp(prefix="badgui_dev_") |
||||||
|
self.build(temp_dir, project_name, **kwargs) # Full rebuild every time |
||||||
|
subprocess.run(["npm", "install"], cwd=temp_dir) # Reinstall dependencies |
||||||
|
``` |
||||||
|
|
||||||
|
**Improvement:** Incremental builds and dependency caching: |
||||||
|
```python |
||||||
|
class DevServer: |
||||||
|
def __init__(self, app: App): |
||||||
|
self.app = app |
||||||
|
self._build_cache = {} |
||||||
|
self._dependency_cache = None |
||||||
|
|
||||||
|
def start(self, port: int = 9000): |
||||||
|
if not self._needs_full_rebuild(): |
||||||
|
self._incremental_build() |
||||||
|
else: |
||||||
|
self._full_build() |
||||||
|
|
||||||
|
if not self._dependency_cache: |
||||||
|
self._install_dependencies() |
||||||
|
``` |
||||||
|
|
||||||
|
#### Issue: String-based Template Generation |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
page_content = f'''<template> |
||||||
|
<q-page class="row items-center justify-evenly"> |
||||||
|
{template_content} |
||||||
|
</q-page> |
||||||
|
</template>''' |
||||||
|
``` |
||||||
|
|
||||||
|
**Improvement:** Template engine with caching: |
||||||
|
```python |
||||||
|
from jinja2 import Environment, FileSystemLoader |
||||||
|
|
||||||
|
class TemplateGenerator: |
||||||
|
def __init__(self): |
||||||
|
self.env = Environment(loader=FileSystemLoader('templates')) |
||||||
|
self._template_cache = {} |
||||||
|
|
||||||
|
def render_page(self, page: Page) -> str: |
||||||
|
template = self.env.get_template('page.vue.j2') |
||||||
|
return template.render(page=page, components=page.components) |
||||||
|
``` |
||||||
|
|
||||||
|
### 8.3 Error Handling and Validation |
||||||
|
|
||||||
|
#### Issue: No Component Validation |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
def button(self, text: str, icon: str = None, **kwargs) -> Component: |
||||||
|
props = {'label': text, **kwargs} |
||||||
|
component = Component('button', **props) |
||||||
|
return self._add_component(component) |
||||||
|
``` |
||||||
|
|
||||||
|
**Improvement:** Schema validation: |
||||||
|
```python |
||||||
|
from pydantic import BaseModel, validator |
||||||
|
|
||||||
|
class ButtonProps(BaseModel): |
||||||
|
label: str |
||||||
|
icon: Optional[str] = None |
||||||
|
color: str = 'primary' |
||||||
|
|
||||||
|
@validator('color') |
||||||
|
def validate_color(cls, v): |
||||||
|
valid_colors = ['primary', 'secondary', 'accent', 'positive', 'negative'] |
||||||
|
if v not in valid_colors: |
||||||
|
raise ValueError(f'Invalid color: {v}') |
||||||
|
return v |
||||||
|
|
||||||
|
def button(self, text: str, **kwargs) -> Component: |
||||||
|
props = ButtonProps(label=text, **kwargs) |
||||||
|
component = Component('button', **props.dict()) |
||||||
|
return self._add_component(component) |
||||||
|
``` |
||||||
|
|
||||||
|
### 8.4 Type Safety and IDE Support |
||||||
|
|
||||||
|
#### Issue: Limited Type Hints |
||||||
|
**Current Code:** |
||||||
|
```python |
||||||
|
def classes(self, class_names): # No type hints |
||||||
|
# Implementation |
||||||
|
``` |
||||||
|
|
||||||
|
**Improvement:** Full type safety: |
||||||
|
```python |
||||||
|
from typing import Union, List, Optional, TYPE_CHECKING |
||||||
|
|
||||||
|
if TYPE_CHECKING: |
||||||
|
from .core import Component |
||||||
|
|
||||||
|
def classes(self, class_names: Union[str, List[str]]) -> 'Component': |
||||||
|
"""Add CSS classes to the component.""" |
||||||
|
# Implementation |
||||||
|
``` |
||||||
|
|
||||||
|
### 8.5 Testing and Debugging |
||||||
|
|
||||||
|
#### Issue: No Built-in Testing Support |
||||||
|
**Improvement:** Add testing utilities: |
||||||
|
```python |
||||||
|
class TestRenderer: |
||||||
|
def __init__(self, app: App): |
||||||
|
self.app = app |
||||||
|
|
||||||
|
def render_component(self, component: Component) -> Dict: |
||||||
|
"""Render component to testable structure.""" |
||||||
|
return { |
||||||
|
'type': component.component_type, |
||||||
|
'props': component._props, |
||||||
|
'classes': component._classes, |
||||||
|
'children': [self.render_component(child) for child in component.children] |
||||||
|
} |
||||||
|
|
||||||
|
def find_components(self, component_type: str) -> List[Component]: |
||||||
|
"""Find all components of a specific type.""" |
||||||
|
# Implementation |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Conclusion |
||||||
|
|
||||||
|
BadGUI provides a solid foundation for generating Vue.js applications from Python code. The workflow is straightforward and functional, but there are several areas where improvements could significantly enhance the framework: |
||||||
|
|
||||||
|
1. **Architecture**: Move away from singleton pattern, implement plugin system |
||||||
|
2. **Performance**: Add incremental builds, template caching, dependency management |
||||||
|
3. **Developer Experience**: Improve error handling, add type safety, better IDE support |
||||||
|
4. **Extensibility**: Plugin-based component system, customizable generators |
||||||
|
5. **Testing**: Built-in testing utilities and debugging tools |
||||||
|
|
||||||
|
The current implementation demonstrates the core concepts well but would benefit from a more modular, extensible architecture for production use. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
*Document generated on: September 28, 2025* |
||||||
|
*BadGUI Version: 0.2.0* |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
""" |
||||||
|
Simple test to debug the handleClick duplication issue |
||||||
|
""" |
||||||
|
|
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "DebugTest", "Debug Test"): |
||||||
|
bg.label("Debug Test").classes("text-h3") |
||||||
|
|
||||||
|
# Just one button with one handler |
||||||
|
button = bg.button("Test Button") |
||||||
|
button.on_click('handleClick') |
||||||
|
|
||||||
|
# Simple script with just one function |
||||||
|
bg.script(""" |
||||||
|
const handleClick = () => { |
||||||
|
console.log('Button clicked!'); |
||||||
|
}; |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
console.log('Debug test loaded'); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
bg.dev(port=3034) |
||||||
@ -0,0 +1,191 @@ |
|||||||
|
""" |
||||||
|
Extended Generic Component Test |
||||||
|
Testing more advanced Quasar components with the bg.component() method. |
||||||
|
""" |
||||||
|
|
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "ExtendedTest", "Extended Generic Component Test"): |
||||||
|
with bg.column() as main_col: |
||||||
|
main_col.classes("q-pa-lg q-gutter-lg") |
||||||
|
bg.label("Extended Quasar Component Test").classes("text-h3 q-mb-lg") |
||||||
|
|
||||||
|
# Date and Time components |
||||||
|
with bg.card(): |
||||||
|
bg.label("Date & Time Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
bg.label("Date Picker:").classes("q-mb-sm") |
||||||
|
date_picker = bg.component('q-date', minimal=True) |
||||||
|
date_picker.bind_value('selectedDate') |
||||||
|
date_picker.classes("q-mb-md") |
||||||
|
|
||||||
|
bg.label("Time Picker:").classes("q-mb-sm") |
||||||
|
time_picker = bg.component('q-time', format24h=True) |
||||||
|
time_picker.bind_value('selectedTime') |
||||||
|
time_picker.classes("q-mb-md") |
||||||
|
|
||||||
|
# Advanced Input Components |
||||||
|
with bg.card(): |
||||||
|
bg.label("Advanced Inputs").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Option Group (Radio buttons) |
||||||
|
bg.label("Select Option:").classes("q-mb-sm") |
||||||
|
option_group = bg.component('q-option-group', |
||||||
|
options=[ |
||||||
|
{'label': 'Option 1', 'value': 'opt1'}, |
||||||
|
{'label': 'Option 2', 'value': 'opt2'}, |
||||||
|
{'label': 'Option 3', 'value': 'opt3'} |
||||||
|
], |
||||||
|
type='radio', |
||||||
|
inline=True) |
||||||
|
option_group.bind_value('selectedOption') |
||||||
|
option_group.classes("q-mb-md") |
||||||
|
|
||||||
|
# File input |
||||||
|
bg.label("File Upload:").classes("q-mb-sm") |
||||||
|
file_input = bg.component('q-file', |
||||||
|
label='Choose file', |
||||||
|
filled=True, |
||||||
|
accept='.jpg,.png,.pdf') |
||||||
|
file_input.bind_value('uploadedFile') |
||||||
|
file_input.classes("q-mb-md") |
||||||
|
|
||||||
|
# Color picker |
||||||
|
bg.label("Color Picker:").classes("q-mb-sm") |
||||||
|
color_picker = bg.component('q-color') |
||||||
|
color_picker.bind_value('selectedColor') |
||||||
|
color_picker.classes("q-mb-md") |
||||||
|
|
||||||
|
# Layout Components |
||||||
|
with bg.card(): |
||||||
|
bg.label("Layout Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Tabs |
||||||
|
with bg.component('q-tabs', |
||||||
|
v_model='activeTab', |
||||||
|
dense=True, |
||||||
|
class_='text-grey', |
||||||
|
active_color='primary', |
||||||
|
indicator_color='primary', |
||||||
|
align='justify', |
||||||
|
narrow_indicator=True, |
||||||
|
container=True) as tabs: |
||||||
|
|
||||||
|
bg.component('q-tab', name='tab1', label='Tab 1') |
||||||
|
bg.component('q-tab', name='tab2', label='Tab 2') |
||||||
|
bg.component('q-tab', name='tab3', label='Tab 3') |
||||||
|
|
||||||
|
# Tab panels |
||||||
|
with bg.component('q-tab-panels', |
||||||
|
v_model='activeTab', |
||||||
|
animated=True, |
||||||
|
container=True): |
||||||
|
|
||||||
|
with bg.component('q-tab-panel', name='tab1', container=True): |
||||||
|
bg.label("Content for Tab 1").classes("text-h6") |
||||||
|
bg.component('q-separator').classes("q-my-md") |
||||||
|
bg.label("This is the first tab content") |
||||||
|
|
||||||
|
with bg.component('q-tab-panel', name='tab2', container=True): |
||||||
|
bg.label("Content for Tab 2").classes("text-h6") |
||||||
|
bg.component('q-separator').classes("q-my-md") |
||||||
|
bg.label("This is the second tab content") |
||||||
|
|
||||||
|
with bg.component('q-tab-panel', name='tab3', container=True): |
||||||
|
bg.label("Content for Tab 3").classes("text-h6") |
||||||
|
bg.component('q-separator').classes("q-my-md") |
||||||
|
bg.label("This is the third tab content") |
||||||
|
|
||||||
|
# Display Components |
||||||
|
with bg.card(): |
||||||
|
bg.label("Display Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Circular progress |
||||||
|
bg.label("Circular Progress:").classes("q-mb-sm") |
||||||
|
with bg.row() as progress_row: |
||||||
|
progress_row.classes("q-gutter-md q-mb-md") |
||||||
|
bg.component('q-circular-progress', |
||||||
|
value=0.25, |
||||||
|
size='50px', |
||||||
|
color='red') |
||||||
|
bg.component('q-circular-progress', |
||||||
|
value=0.5, |
||||||
|
size='50px', |
||||||
|
color='orange') |
||||||
|
bg.component('q-circular-progress', |
||||||
|
value=0.75, |
||||||
|
size='50px', |
||||||
|
color='green') |
||||||
|
|
||||||
|
# Chip components |
||||||
|
bg.label("Chips:").classes("q-mb-sm") |
||||||
|
with bg.row() as chip_row: |
||||||
|
chip_row.classes("q-gutter-sm q-mb-md") |
||||||
|
bg.component('q-chip', label='Tag 1', removable=True, color='primary') |
||||||
|
bg.component('q-chip', label='Tag 2', removable=True, color='secondary') |
||||||
|
bg.component('q-chip', label='Tag 3', removable=True, color='accent') |
||||||
|
|
||||||
|
# Avatar |
||||||
|
bg.label("Avatar:").classes("q-mb-sm") |
||||||
|
bg.component('q-avatar', size='64px', color='teal', text_color='white', icon='person') |
||||||
|
|
||||||
|
# Results display |
||||||
|
with bg.card(): |
||||||
|
bg.label("Current Values").classes("text-h5 q-mb-md") |
||||||
|
results = bg.label("Click 'Update Display' to see values") |
||||||
|
results.bind_text('displayResults') |
||||||
|
results.classes("text-body1 q-mb-md") |
||||||
|
|
||||||
|
bg.button("Update Display").on_click('updateDisplay').classes("q-btn-primary") |
||||||
|
|
||||||
|
# JavaScript for the extended components |
||||||
|
bg.script(""" |
||||||
|
// Reactive data |
||||||
|
const selectedDate = ref(null); |
||||||
|
const selectedTime = ref(null); |
||||||
|
const selectedOption = ref('opt1'); |
||||||
|
const uploadedFile = ref(null); |
||||||
|
const selectedColor = ref('#1976D2'); |
||||||
|
const activeTab = ref('tab1'); |
||||||
|
const displayResults = ref('Click "Update Display" to see values'); |
||||||
|
|
||||||
|
// Update function |
||||||
|
const updateDisplay = () => { |
||||||
|
const results = [ |
||||||
|
`Date: ${selectedDate.value || 'Not selected'}`, |
||||||
|
`Time: ${selectedTime.value || 'Not selected'}`, |
||||||
|
`Option: ${selectedOption.value}`, |
||||||
|
`File: ${uploadedFile.value ? uploadedFile.value.name : 'None'}`, |
||||||
|
`Color: ${selectedColor.value}`, |
||||||
|
`Active Tab: ${activeTab.value}` |
||||||
|
]; |
||||||
|
displayResults.value = results.join(' • '); |
||||||
|
}; |
||||||
|
|
||||||
|
// Watch for changes |
||||||
|
watch([selectedDate, selectedTime, selectedOption, uploadedFile, selectedColor, activeTab], |
||||||
|
updateDisplay, { deep: true }); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
console.log('Extended component test loaded!'); |
||||||
|
console.log('Testing advanced Quasar components:'); |
||||||
|
console.log('- q-date, q-time'); |
||||||
|
console.log('- q-option-group, q-file, q-color'); |
||||||
|
console.log('- q-tabs, q-tab-panels'); |
||||||
|
console.log('- q-circular-progress, q-chip, q-avatar'); |
||||||
|
updateDisplay(); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
print("🧪 Extended Generic Component Test") |
||||||
|
print("Testing advanced Quasar components with bg.component()") |
||||||
|
print() |
||||||
|
print("Components being tested:") |
||||||
|
print("📅 Date & Time: q-date, q-time") |
||||||
|
print("📝 Advanced Inputs: q-option-group, q-file, q-color") |
||||||
|
print("📑 Layout: q-tabs, q-tab-panels") |
||||||
|
print("📊 Display: q-circular-progress, q-chip, q-avatar") |
||||||
|
|
||||||
|
# Run dev server |
||||||
|
bg.dev(port=3032) |
||||||
@ -0,0 +1,144 @@ |
|||||||
|
""" |
||||||
|
Generic Component Test |
||||||
|
Demonstrates the new bg.component() method for creating any Quasar component. |
||||||
|
""" |
||||||
|
|
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "GenericComponentTest", "Generic Component Test"): |
||||||
|
with bg.column() as main_col: |
||||||
|
main_col.classes("q-pa-lg q-gutter-md") |
||||||
|
bg.label("Generic Quasar Component Test").classes("text-h3 q-mb-lg") |
||||||
|
|
||||||
|
with bg.card(): |
||||||
|
bg.label("Form Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Rating component |
||||||
|
bg.label("Rating:").classes("q-mb-sm") |
||||||
|
rating = bg.component('q-rating', max=5, size='2em', color='amber') |
||||||
|
rating.bind_value('userRating') |
||||||
|
rating.classes("q-mb-md") |
||||||
|
|
||||||
|
# Select component |
||||||
|
bg.label("Select an option:").classes("q-mb-sm") |
||||||
|
select = bg.component('q-select', |
||||||
|
filled=True, |
||||||
|
options=['Option 1', 'Option 2', 'Option 3'], |
||||||
|
label='Choose one') |
||||||
|
select.bind_value('selectedOption') |
||||||
|
select.classes("q-mb-md") |
||||||
|
|
||||||
|
# Slider component |
||||||
|
bg.label("Volume:").classes("q-mb-sm") |
||||||
|
slider = bg.component('q-slider', |
||||||
|
min=0, |
||||||
|
max=100, |
||||||
|
step=10, |
||||||
|
markers=True, |
||||||
|
color='primary') |
||||||
|
slider.bind_value('volumeLevel') |
||||||
|
slider.classes("q-mb-md") |
||||||
|
|
||||||
|
# Toggle component |
||||||
|
toggle = bg.component('q-toggle', |
||||||
|
label='Enable notifications', |
||||||
|
color='green') |
||||||
|
toggle.bind_value('notificationsEnabled') |
||||||
|
toggle.classes("q-mb-md") |
||||||
|
|
||||||
|
with bg.card(): |
||||||
|
bg.label("Display Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Progress bar |
||||||
|
bg.label("Progress:").classes("q-mb-sm") |
||||||
|
progress = bg.component('q-linear-progress', |
||||||
|
size='20px', |
||||||
|
color='secondary', |
||||||
|
value=0.6) |
||||||
|
progress.classes("q-mb-md") |
||||||
|
|
||||||
|
# Badge |
||||||
|
btn = bg.component('q-btn', label='Messages', container=False) |
||||||
|
btn.classes("q-mb-md") |
||||||
|
|
||||||
|
# Note: q-badge with floating=True should be added as a separate component |
||||||
|
# since it positions itself relative to the previous component |
||||||
|
bg.component('q-badge', color='red', floating=True, label='5') |
||||||
|
|
||||||
|
with bg.card(): |
||||||
|
bg.label("Container Components").classes("text-h5 q-mb-md") |
||||||
|
|
||||||
|
# Expansion item (container component) |
||||||
|
with bg.component('q-expansion-item', |
||||||
|
label='Advanced Settings', |
||||||
|
icon='settings', |
||||||
|
container=True) as expansion: |
||||||
|
bg.label("These are advanced settings").classes("q-pa-md") |
||||||
|
|
||||||
|
# Nested components |
||||||
|
bg.component('q-separator').classes("q-my-md") |
||||||
|
|
||||||
|
with bg.row(): |
||||||
|
bg.component('q-checkbox', label='Option A', val='a').bind_value('optionA') |
||||||
|
bg.component('q-checkbox', label='Option B', val='b').bind_value('optionB') |
||||||
|
|
||||||
|
# Results display |
||||||
|
with bg.card(): |
||||||
|
bg.label("Current Values").classes("text-h5 q-mb-md") |
||||||
|
result_display = bg.label("Values will appear here") |
||||||
|
result_display.bind_text('displayResults') |
||||||
|
result_display.classes("text-body1") |
||||||
|
|
||||||
|
bg.button("Update Display").on_click('updateDisplay').classes("q-btn-primary q-mt-md") |
||||||
|
|
||||||
|
# JavaScript to handle the generic components |
||||||
|
bg.script(""" |
||||||
|
// Reactive data for all the generic components |
||||||
|
const userRating = ref(3); |
||||||
|
const selectedOption = ref(null); |
||||||
|
const volumeLevel = ref(50); |
||||||
|
const notificationsEnabled = ref(false); |
||||||
|
const optionA = ref(false); |
||||||
|
const optionB = ref(false); |
||||||
|
const displayResults = ref('Values will appear here'); |
||||||
|
|
||||||
|
// Update display function |
||||||
|
const updateDisplay = () => { |
||||||
|
const results = [ |
||||||
|
`Rating: ${userRating.value}/5 stars`, |
||||||
|
`Selected: ${selectedOption.value || 'None'}`, |
||||||
|
`Volume: ${volumeLevel.value}%`, |
||||||
|
`Notifications: ${notificationsEnabled.value ? 'On' : 'Off'}`, |
||||||
|
`Options: A=${optionA.value}, B=${optionB.value}` |
||||||
|
]; |
||||||
|
displayResults.value = results.join(' • '); |
||||||
|
}; |
||||||
|
|
||||||
|
// Watch for changes and auto-update |
||||||
|
watch([userRating, selectedOption, volumeLevel, notificationsEnabled, optionA, optionB], |
||||||
|
updateDisplay, { immediate: true }); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
console.log('Generic component test loaded!'); |
||||||
|
console.log('Available Quasar components can be created with bg.component()'); |
||||||
|
updateDisplay(); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
print("🧪 Generic Component Test") |
||||||
|
print("Testing bg.component() method for creating any Quasar component") |
||||||
|
print() |
||||||
|
print("Examples of components created:") |
||||||
|
print("- q-rating (rating selector)") |
||||||
|
print("- q-select (dropdown)") |
||||||
|
print("- q-slider (range input)") |
||||||
|
print("- q-toggle (switch)") |
||||||
|
print("- q-linear-progress (progress bar)") |
||||||
|
print("- q-badge (notification badge)") |
||||||
|
print("- q-expansion-item (collapsible container)") |
||||||
|
print("- q-separator (divider)") |
||||||
|
print("- q-checkbox (checkbox)") |
||||||
|
|
||||||
|
# Run dev server |
||||||
|
bg.dev(port=3028) |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
""" |
||||||
|
Test refactored components using generic component() as base |
||||||
|
""" |
||||||
|
|
||||||
|
import badgui as bg |
||||||
|
|
||||||
|
with bg.page("/", "RefactoredTest", "Refactored Components Test"): |
||||||
|
with bg.column() as main_col: |
||||||
|
main_col.classes("q-pa-lg q-gutter-md") |
||||||
|
|
||||||
|
bg.label("Refactored Components Test").classes("text-h3 q-mb-lg") |
||||||
|
|
||||||
|
# Test basic components |
||||||
|
with bg.card(title="Basic Components") as card1: |
||||||
|
card1.classes("q-mb-md") |
||||||
|
|
||||||
|
bg.label("This is a label created with the refactored method") |
||||||
|
|
||||||
|
input_field = bg.input("Enter some text here") |
||||||
|
input_field.bind_value('userText') |
||||||
|
input_field.classes("q-mb-md") |
||||||
|
|
||||||
|
button = bg.button("Click Me", icon="thumb_up") |
||||||
|
button.on_click('handleClick') |
||||||
|
|
||||||
|
# Test layout components |
||||||
|
with bg.card(title="Layout Components") as card2: |
||||||
|
card2.classes("q-mb-md") |
||||||
|
|
||||||
|
bg.label("Row layout:") |
||||||
|
with bg.row() as test_row: |
||||||
|
test_row.classes("q-gutter-md q-mb-md") |
||||||
|
|
||||||
|
bg.label("Item 1").classes("col") |
||||||
|
bg.label("Item 2").classes("col") |
||||||
|
bg.label("Item 3").classes("col") |
||||||
|
|
||||||
|
bg.label("Column layout:") |
||||||
|
with bg.column() as test_col: |
||||||
|
test_col.classes("q-gutter-sm") |
||||||
|
|
||||||
|
bg.label("Column Item 1") |
||||||
|
bg.label("Column Item 2") |
||||||
|
bg.label("Column Item 3") |
||||||
|
|
||||||
|
# Test mixed with generic components |
||||||
|
with bg.card(title="Mixed Components") as card3: |
||||||
|
card3.classes("q-mb-md") |
||||||
|
|
||||||
|
bg.label("Regular button vs Generic button:") |
||||||
|
|
||||||
|
with bg.row() as button_row: |
||||||
|
button_row.classes("q-gutter-md") |
||||||
|
|
||||||
|
# Regular button (refactored) |
||||||
|
regular_btn = bg.button("Regular Button") |
||||||
|
regular_btn.on_click('handleRegular') |
||||||
|
|
||||||
|
# Generic button |
||||||
|
generic_btn = bg.component('q-btn', label='Generic Button', color='secondary') |
||||||
|
generic_btn.on_click('handleGeneric') |
||||||
|
|
||||||
|
# JavaScript |
||||||
|
bg.script(""" |
||||||
|
const userText = ref(''); |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
console.log('Refactored button clicked!'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleRegular = () => { |
||||||
|
console.log('Regular (refactored) button clicked!'); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleGeneric = () => { |
||||||
|
console.log('Generic button clicked!'); |
||||||
|
}; |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
console.log('Refactored components test loaded!'); |
||||||
|
console.log('All components now use the generic component() method as their base'); |
||||||
|
}); |
||||||
|
""") |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
print("🔧 Refactored Components Test") |
||||||
|
print("Testing components that use generic component() as their base") |
||||||
|
print() |
||||||
|
print("This demonstrates:") |
||||||
|
print("- Consistent behavior across all components") |
||||||
|
print("- Reduced code duplication") |
||||||
|
print("- Easier maintenance") |
||||||
|
|
||||||
|
bg.dev(port=3033) |
||||||
Loading…
Reference in new issue