Fix React Hooks order violation and drag-drop functionality
This commit is contained in:
parent
be6bf28b7e
commit
dade418940
3 changed files with 133 additions and 32 deletions
93
SUBTITLE_UI_TODO.md
Normal file
93
SUBTITLE_UI_TODO.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Subtitle Generator - Missing UI Features
|
||||
|
||||
## Current Status
|
||||
The **backend** (`subtitle_processor.py`) has full support for subtitle styling options, but the **frontend** (`/video/subtitles/page.tsx`) is missing the UI controls to expose these features.
|
||||
|
||||
## Missing UI Controls
|
||||
|
||||
### Font Options
|
||||
- [ ] **Font Family** dropdown (24+ fonts supported)
|
||||
- Arial, Helvetica, Times New Roman, Courier New, Verdana, Georgia, Comic Sans MS, Impact, Tahoma, Trebuchet MS, Lucida Sans, Lucida Console, Palatino Linotype, Book Antiqua, Century Gothic, Franklin Gothic, Garamond, Segoe UI, Calibri, Cambria, Candara, Constantia, Consolas, Corbel
|
||||
- [ ] **Font Size** number input (8-72, default: 24)
|
||||
|
||||
### Color Options
|
||||
- [ ] **Text Color** dropdown
|
||||
- white, yellow, black, red, blue, green, orange, purple
|
||||
- [ ] **Outline Color** dropdown
|
||||
- black, white, yellow, red, blue, green
|
||||
- [ ] **Outline Width** number input (0-4, default: 1, step: 0.1)
|
||||
|
||||
### Position Options
|
||||
- [ ] **Position** dropdown (bottom/top)
|
||||
- [ ] **Alignment** dropdown (left/center/right) - currently not exposed
|
||||
- [ ] **Vertical Margin** number input - currently not exposed
|
||||
- [ ] **Horizontal Margin** number input - currently not exposed
|
||||
|
||||
### Advanced Options (Backend Supported)
|
||||
- [ ] **Background Color** dropdown (optional)
|
||||
- [ ] **Background Opacity** slider (0-1)
|
||||
- [ ] **Shadow** number input (0-4)
|
||||
- [ ] **Bold** checkbox
|
||||
- [ ] **Italic** checkbox
|
||||
- [ ] **Font Preset** dropdown (default, cinematic, documentary, news, social_media, minimal, bold)
|
||||
|
||||
### Model Options
|
||||
- [ ] **Whisper Model** dropdown
|
||||
- tiny (fastest), base (default), small, medium, large, large-v2, large-v3
|
||||
- [ ] **Output Format** dropdown
|
||||
- SRT (default), VTT, ASS
|
||||
- [ ] **Word Timestamps** checkbox
|
||||
|
||||
## Backend Parameters (Already Implemented)
|
||||
```python
|
||||
{
|
||||
"source_language": "auto",
|
||||
"target_language": "EN-US",
|
||||
"burn_subtitles": true,
|
||||
"whisper_model": "base",
|
||||
"font": "Arial",
|
||||
"font_size": 24,
|
||||
"text_color": "white",
|
||||
"outline_color": "black",
|
||||
"outline_width": 2,
|
||||
"position": "bottom",
|
||||
"alignment": "center",
|
||||
"margin_v": 30,
|
||||
"margin_h": 20,
|
||||
"shadow": 0,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"background_color": null,
|
||||
"background_opacity": 0,
|
||||
"font_preset": null,
|
||||
"word_timestamps": false,
|
||||
"output_format": "srt"
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended UI Layout
|
||||
Match the original sandbox implementation structure:
|
||||
1. **File Upload** (✅ exists)
|
||||
2. **Language Selection** (✅ exists)
|
||||
3. **Subtitle Styling Section** (❌ missing)
|
||||
- Font & Size (row)
|
||||
- Text Color & Outline Color (row)
|
||||
- Outline Width & Position (row)
|
||||
4. **Advanced Options** (collapsible) (❌ missing)
|
||||
- Whisper Model
|
||||
- Output Format
|
||||
- Background options
|
||||
- Text styling (bold/italic)
|
||||
5. **Burn Subtitles** checkbox (✅ exists)
|
||||
6. **Process Button** (✅ exists)
|
||||
|
||||
## Next Steps
|
||||
1. Add a "Subtitle Styling" section to the frontend
|
||||
2. Add form controls for all missing options
|
||||
3. Update the `modulesApi.processSubtitles()` call to include all parameters
|
||||
4. Test with different styling combinations
|
||||
5. Consider adding a live preview of subtitle styling
|
||||
|
||||
## Files to Modify
|
||||
- `/forge-ai/frontend/app/video/subtitles/page.tsx` - Add UI controls
|
||||
- `/forge-ai/frontend/lib/api.ts` - Ensure processSubtitles accepts all parameters (likely already does)
|
||||
|
|
@ -92,21 +92,7 @@ export default function ImageUpscalePage() {
|
|||
}
|
||||
}, [searchParams]);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleFileUpload = (files: File[]) => {
|
||||
const newItems: QueueItem[] = files.map(file => ({
|
||||
id: Math.random().toString(36).substring(7),
|
||||
file,
|
||||
status: 'pending'
|
||||
}));
|
||||
setQueue(prev => [...prev, ...newItems]);
|
||||
toast.success(`${files.length} images added to queue`);
|
||||
};
|
||||
|
||||
// Handle drag-and-drop from carousel
|
||||
// Handle drag-and-drop from carousel - MUST be at top level, not conditional
|
||||
useDragFromCarousel({
|
||||
onAssetDrop: async (assetIds) => {
|
||||
try {
|
||||
|
|
@ -138,6 +124,20 @@ export default function ImageUpscalePage() {
|
|||
enabled: mounted
|
||||
});
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleFileUpload = (files: File[]) => {
|
||||
const newItems: QueueItem[] = files.map(file => ({
|
||||
id: Math.random().toString(36).substring(7),
|
||||
file,
|
||||
status: 'pending'
|
||||
}));
|
||||
setQueue(prev => [...prev, ...newItems]);
|
||||
toast.success(`${files.length} images added to queue`);
|
||||
};
|
||||
|
||||
const processItem = async (item: QueueItem) => {
|
||||
if (item.status === 'completed' || item.status === 'processing') return item;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,39 +16,47 @@ export function useDragFromCarousel({ onAssetDrop, enabled = true }: UseDragFrom
|
|||
|
||||
const handleDragOver = (e: DragEvent) => {
|
||||
// Check if dragging from carousel (has asset-id data)
|
||||
const assetId = e.dataTransfer?.getData('application/x-asset-id');
|
||||
if (assetId) {
|
||||
const types = Array.from(e.dataTransfer?.types || []);
|
||||
if (types.includes('application/x-asset-id')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer!.dropEffect = 'copy';
|
||||
|
||||
// Add visual feedback
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.add('drag-over');
|
||||
if (target) {
|
||||
target.classList.add('drag-over');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: DragEvent) => {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.remove('drag-over');
|
||||
if (target) {
|
||||
target.classList.remove('drag-over');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const types = Array.from(e.dataTransfer?.types || []);
|
||||
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
target.classList.remove('drag-over');
|
||||
// Only handle if it's our asset drag
|
||||
if (types.includes('application/x-asset-id')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Get asset IDs from drag data
|
||||
const assetId = e.dataTransfer?.getData('application/x-asset-id');
|
||||
const assetIds = e.dataTransfer?.getData('application/x-asset-ids');
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
if (target) {
|
||||
target.classList.remove('drag-over');
|
||||
}
|
||||
|
||||
if (assetIds) {
|
||||
// Multiple assets
|
||||
onAssetDrop(JSON.parse(assetIds));
|
||||
} else if (assetId) {
|
||||
// Single asset
|
||||
onAssetDrop([assetId]);
|
||||
// Get asset ID from drag data
|
||||
const assetId = e.dataTransfer?.getData('application/x-asset-id');
|
||||
|
||||
if (assetId) {
|
||||
// Single asset
|
||||
onAssetDrop([assetId]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue