diff --git a/example_file_drop.py b/example_file_drop.py index d3420c9..23eab85 100644 --- a/example_file_drop.py +++ b/example_file_drop.py @@ -4,6 +4,7 @@ import base64 from PIL import Image import io from niceguiex.components import FileDrop, ImageDrop +from niceguiex.components.file_drop import FileData, ImageData @ui.page('/') @@ -13,14 +14,12 @@ async def main_page(): # Example 1: Multiple files with content uploaded_files = ui.column().classes('w-full mt-4') - def handle_multiple_files(files: List[Dict[str, Any]]): + def handle_multiple_files(file: FileData): with uploaded_files: - ui.label(f'Uploaded {len(files)} file(s):').classes('font-bold mb-2') - for file in files: - with ui.card().classes('p-2 mb-2'): - ui.label(f"📄 {file['name']}").classes('font-medium') - ui.label(f"Size: {file['size']:,} bytes").classes('text-sm text-gray-600') - ui.label(f"Type: {file['type']}").classes('text-sm text-gray-600') + with ui.card().classes('p-2 mb-2'): + ui.label(f"📄 {file['name']}").classes('font-medium') + ui.label(f"Size: {file['size']:,} bytes").classes('text-sm text-gray-600') + ui.label(f"Type: {file['file_type']}").classes('text-sm text-gray-600') ui.label('Multiple Files (returns list)').classes('text-h6 mb-2') FileDrop( @@ -73,16 +72,16 @@ async def main_page(): # Example 4: Image drop with preview image_preview = ui.column().classes('w-full mt-4') - def handle_image(img: Image.Image): - print(f"Image uploaded: {img.format}, Size: {img.size}") + def handle_image(img: ImageData): + print(f"Image uploaded: {img['image'].format}, Size: {img['size']}") image_preview.clear() with image_preview: - ui.label(f"Image: {img.format} - {img.size[0]}x{img.size[1]}px").classes('mb-2') + ui.label(f"Image: {img['image'].format} - {img['image'].size[0]}x{img['image'].size[1]}px").classes('mb-2') # Convert PIL image back to base64 for display buffered = io.BytesIO() - img.save(buffered, format=img.format or 'PNG') + img['image'].save(buffered, format=img['image'].format or 'PNG') img_data = base64.b64encode(buffered.getvalue()).decode() - ui.html(f'') + ui.html(f'') ui.label('Image Drop (returns PIL Image)').classes('text-h6 mb-2') ImageDrop( diff --git a/src/niceguiex/components/__init__.py b/src/niceguiex/components/__init__.py index 1c43d5f..88c13d2 100644 --- a/src/niceguiex/components/__init__.py +++ b/src/niceguiex/components/__init__.py @@ -1,5 +1,5 @@ from .auto_scroll_area import AutoScrollArea from .chat_input import ChatInput -from .file_drop import FileDrop, ImageDrop +from .file_drop import FileDrop, ImageDrop, FileData, ImageData -__all__ = ['AutoScrollArea', 'ChatInput', 'FileDrop', 'ImageDrop'] +__all__ = ['AutoScrollArea', 'ChatInput', 'FileDrop', 'ImageDrop', 'FileData', 'ImageData'] diff --git a/src/niceguiex/components/file_drop.py b/src/niceguiex/components/file_drop.py index 2e03f69..6972034 100644 --- a/src/niceguiex/components/file_drop.py +++ b/src/niceguiex/components/file_drop.py @@ -1,11 +1,26 @@ from nicegui import ui -from typing import Optional, Callable +from typing import Optional, Callable, TypedDict, Any from pathlib import Path import tempfile from PIL import Image import io +class FileData(TypedDict): + name: str + size: int + file_type: str + content: Optional[Any] + path: Optional[Path] + + +class ImageData(TypedDict): + name: str + size: int + file_type: str + image: Image.Image + + class FileDrop(ui.element): def __init__(self, on_upload: Optional[Callable] = None, @@ -100,24 +115,13 @@ class FileDrop(ui.element): # Process single file upload if hasattr(e, 'content'): + print('content') file_data = self._process_file(e) if file_data: # For single file mode, return the dict directly - if not self.multiple: - self.on_upload_callback(file_data) - else: - self.on_upload_callback([file_data]) - # Handle multiple files - elif hasattr(e, 'files'): - files = [] - for file_info in e.files: - file_data = self._process_file(file_info) - if file_data: - files.append(file_data) - if files: - self.on_upload_callback(files) + self.on_upload_callback(file_data) - def _process_file(self, file_obj): + def _process_file(self, file_obj) -> FileData | None: """Process a single file object""" if not hasattr(file_obj, 'content'): return None @@ -130,11 +134,11 @@ class FileDrop(ui.element): ui.notify(f"File too large. Max size is {self.max_size}MB", type='negative') return None - file_data = { - 'name': file_obj.name if hasattr(file_obj, 'name') else 'unknown', - 'size': size, - 'type': file_obj.type if hasattr(file_obj, 'type') else 'application/octet-stream', - } + file_data: FileData = {'name': file_obj.name if hasattr(file_obj, 'name') else 'unknown', + 'size': size, + 'file_type': file_obj.type if hasattr(file_obj, 'type') else 'application/octet-stream', + 'content': None, + 'path': None} # Return content or temp file path based on settings if self.return_content: @@ -171,7 +175,7 @@ class ImageDrop(FileDrop): *args, **kwargs ) - def _handle_image_upload(self, data): + def _handle_image_upload(self, data: FileData): """Convert file data to PIL Images before calling user callback""" if self._user_callback: if isinstance(data, list) and len(data) == 1: @@ -186,5 +190,9 @@ class ImageDrop(FileDrop): self._user_callback(images) else: # Single image - convert to PIL Image - img = Image.open(io.BytesIO(data['content'])) - self._user_callback(img) + image_data: ImageData = {'name': data['name'], + 'size': data['size'], + 'file_type': data['file_type'], + 'image': Image.open(io.BytesIO(data['content']))} + # img = Image.open(io.BytesIO(data['content'])) + self._user_callback(image_data)