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