mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-06 14:11:35 +02:00
- Add rename functionality for files and folders:
- New RenameModal component with validation - Backend endpoint supports filename updates (rejects path separators) - Context menu "Rename" option in grid and list views - Inline rename button in list view actions - Add Print button to multi-selection toolbar: - Appears when exactly one sliced file is selected - Opens full PrintModal with plate selection and options - Add to Queue button now uses Clock icon for clarity - Improve mobile/PWA accessibility: - Three-dot menu button always visible on mobile (md:opacity-0) - Selection checkbox always visible on touch devices - Better experience for PWA users on tablets and phones Closes #94
This commit is contained in:
@@ -58,6 +58,18 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- "Embedded": Shows camera as a floating overlay on the main screen
|
||||
- Embedded viewer is draggable and resizable with persistent position/size
|
||||
- Configure in Settings → General → Camera section
|
||||
- **File Manager Rename** - Rename files and folders directly in File Manager:
|
||||
- Right-click context menu "Rename" option for files and folders
|
||||
- Inline rename button in list view
|
||||
- Validates filenames (no path separators allowed)
|
||||
- **File Manager Print Button** - Print directly from multi-selection toolbar:
|
||||
- "Print" button appears when exactly one sliced file is selected
|
||||
- Opens full PrintModal with plate selection and print options
|
||||
- "Add to Queue" button now uses Clock icon for clarity
|
||||
- **File Manager Mobile Accessibility** - Improved touch device support:
|
||||
- Three-dot menu button always visible on mobile (hover-only on desktop)
|
||||
- Selection checkbox always visible on mobile devices
|
||||
- Better PWA experience for file management
|
||||
|
||||
### Changed
|
||||
- **Edit Queue Item modal** - Single printer selection only (reassigns item, doesn't duplicate)
|
||||
|
||||
@@ -82,10 +82,12 @@
|
||||
### 📁 File Manager (Library)
|
||||
- Upload and organize sliced files (3MF, gcode)
|
||||
- Folder structure with drag-and-drop
|
||||
- Rename files and folders via context menu
|
||||
- Print directly to any printer with full options
|
||||
- Add to queue without creating archive upfront
|
||||
- Plate selection for multi-plate 3MF files
|
||||
- Duplicate detection via file hash
|
||||
- Mobile-friendly with always-visible action buttons
|
||||
|
||||
### 📁 Projects
|
||||
- Group related prints (e.g., "Voron Build")
|
||||
|
||||
@@ -1406,6 +1406,12 @@ async def update_file(file_id: int, data: FileUpdate, db: AsyncSession = Depends
|
||||
if not file:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
if data.filename is not None:
|
||||
# Validate filename doesn't contain path separators
|
||||
if "/" in data.filename or "\\" in data.filename:
|
||||
raise HTTPException(status_code=400, detail="Filename cannot contain path separators")
|
||||
file.filename = data.filename
|
||||
|
||||
if data.folder_id is not None:
|
||||
if data.folder_id == 0:
|
||||
file.folder_id = None
|
||||
|
||||
@@ -80,6 +80,7 @@ class FileCreate(BaseModel):
|
||||
class FileUpdate(BaseModel):
|
||||
"""Schema for updating a file."""
|
||||
|
||||
filename: str | None = Field(None, min_length=1, max_length=255)
|
||||
folder_id: int | None = None
|
||||
project_id: int | None = None
|
||||
notes: str | None = None
|
||||
|
||||
@@ -204,6 +204,37 @@ class TestLibraryFilesAPI:
|
||||
result = response.json()
|
||||
assert result.get("message") or result.get("success", True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_rename_file(self, async_client: AsyncClient, file_factory, db_session):
|
||||
"""Verify file can be renamed."""
|
||||
lib_file = await file_factory(filename="old_name.3mf")
|
||||
data = {"filename": "new_name.3mf"}
|
||||
response = await async_client.put(f"/api/v1/library/files/{lib_file.id}", json=data)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
assert result["filename"] == "new_name.3mf"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_rename_file_invalid_path_separator(self, async_client: AsyncClient, file_factory, db_session):
|
||||
"""Verify file rename fails with path separators."""
|
||||
lib_file = await file_factory(filename="test.3mf")
|
||||
data = {"filename": "path/to/file.3mf"}
|
||||
response = await async_client.put(f"/api/v1/library/files/{lib_file.id}", json=data)
|
||||
assert response.status_code == 400
|
||||
assert "path separator" in response.json()["detail"].lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_rename_file_invalid_backslash(self, async_client: AsyncClient, file_factory, db_session):
|
||||
"""Verify file rename fails with backslash."""
|
||||
lib_file = await file_factory(filename="test.3mf")
|
||||
data = {"filename": "path\\to\\file.3mf"}
|
||||
response = await async_client.put(f"/api/v1/library/files/{lib_file.id}", json=data)
|
||||
assert response.status_code == 400
|
||||
assert "path separator" in response.json()["detail"].lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.integration
|
||||
async def test_library_stats(self, async_client: AsyncClient, folder_factory, file_factory, db_session):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -23,8 +23,8 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-Cw4dIiLT.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C__bvgqz.css">
|
||||
<script type="module" crossorigin src="/assets/index-BL-2WI3B.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BmODu1qm.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user