mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 23:32:55 +01:00
257 lines
11 KiB
Markdown
257 lines
11 KiB
Markdown
# TASK-008C: ComponentsPanel Drag-Drop System
|
|
|
|
## Overview
|
|
|
|
This subtask addresses the systematic implementation and debugging of the drag-drop system for the React-based ComponentsPanel. Previous attempts have been piecemeal, leading to circular debugging. This document provides a complete scope and test matrix.
|
|
|
|
---
|
|
|
|
## Expected Behaviors (Full Requirements)
|
|
|
|
### A. DRAG INITIATION
|
|
|
|
| Requirement | Description |
|
|
| ----------- | ------------------------------------------------------------------------------------------ |
|
|
| A1 | Click + hold on any **component** → initiates drag |
|
|
| A2 | Click + hold on any **folder** → initiates drag |
|
|
| A3 | Click + hold on any **component-folder** (component with nested children) → initiates drag |
|
|
| A4 | Visual feedback: dragged item follows cursor with ghost/label |
|
|
| A5 | Drag threshold: 5px movement before drag activates (prevents accidental drags) |
|
|
|
|
### B. DROP TARGETS
|
|
|
|
| ID | Source | Target | Result | Example |
|
|
| --- | ---------------- | ------------------ | ----------------------------------------------- | -------------------------------------------------- |
|
|
| B1 | Component | Component | Creates nesting | `/PageA` → `/PageB` = `/PageB/PageA` |
|
|
| B2 | Component | Folder | Moves into folder | `/MyComp` → `/Folder/` = `/Folder/MyComp` |
|
|
| B3 | Component | Empty Space (Root) | Moves to root level | `/Folder/MyComp` → root = `/MyComp` |
|
|
| B4 | Folder | Folder | Moves folder + all contents | `/FolderA/` → `/FolderB/` = `/FolderB/FolderA/...` |
|
|
| B5 | Folder | Component | Nests folder inside component | `/FolderA/` → `/PageB` = `/PageB/FolderA/...` |
|
|
| B6 | Folder | Empty Space (Root) | Moves folder to root | `/Parent/FolderA/` → root = `/FolderA/...` |
|
|
| B7 | Component-Folder | Any target | Same as folder (moves component + all children) | Same as B4/B5/B6 |
|
|
|
|
### C. VALIDATION
|
|
|
|
| Requirement | Description |
|
|
| ----------- | --------------------------------------------------------------- |
|
|
| C1 | Cannot drop item onto itself |
|
|
| C2 | Cannot drop parent into its own descendant (circular reference) |
|
|
| C3 | Cannot create naming conflicts (same name at same level) |
|
|
| C4 | Show "forbidden" cursor when drop not allowed |
|
|
|
|
### D. VISUAL FEEDBACK
|
|
|
|
| Requirement | Description |
|
|
| ----------- | ------------------------------------------------------------------ |
|
|
| D1 | Hover over valid target → highlight with border/background |
|
|
| D2 | Hover over invalid target → show forbidden indicator |
|
|
| D3 | Hover over empty space → show root drop zone indicator (blue tint) |
|
|
| D4 | Cursor changes based on drop validity (`move` vs `none`) |
|
|
|
|
### E. COMPLETION
|
|
|
|
| Requirement | Description |
|
|
| ----------- | ------------------------------------------------------------- |
|
|
| E1 | Successful drop → item moves, tree re-renders at new location |
|
|
| E2 | Failed/cancelled drop → item returns to origin (no change) |
|
|
| E3 | All operations support Undo (Cmd+Z) |
|
|
| E4 | All operations support Redo (Cmd+Shift+Z) |
|
|
|
|
---
|
|
|
|
## Current Implementation Status
|
|
|
|
### Code Inventory
|
|
|
|
| File | Purpose |
|
|
| -------------------------- | -------------------------------------------------------------------- |
|
|
| `useDragDrop.ts` | React hook managing drag state, uses PopupLayer.startDragging |
|
|
| `useComponentActions.ts` | Drop handlers: `handleDropOn()`, `handleDropOnRoot()` |
|
|
| `ComponentItem.tsx` | Drag initiation + drop target handlers for components |
|
|
| `FolderItem.tsx` | Drag initiation + drop target handlers for folders |
|
|
| `ComponentsPanelReact.tsx` | Background drop zone handlers |
|
|
| `popuplayer.js` | Legacy jQuery drag system (startDragging, indicateDropType, endDrag) |
|
|
|
|
### Feature Status Matrix
|
|
|
|
| Feature | Handler Exists | Wired Up | Tested | Works? |
|
|
| -------------------------- | ---------------------- | -------- | ------ | ------ |
|
|
| B1: Component → Component | ✅ `handleDropOn` | ✅ | ⏳ | ❓ |
|
|
| B2: Component → Folder | ✅ `handleDropOn` | ✅ | ⏳ | ❓ |
|
|
| B3: Component → Root | ✅ `handleDropOnRoot` | ✅ | ⏳ | ❌ |
|
|
| B4: Folder → Folder | ✅ `handleDropOn` | ✅ | ⏳ | ❓ |
|
|
| B5: Folder → Component | ✅ `handleDropOn` | ✅ | ⏳ | ❌ |
|
|
| B6: Folder → Root | ✅ `handleDropOnRoot` | ✅ | ⏳ | ❌ |
|
|
| B7: Component-Folder → any | ✅ (handled as folder) | ✅ | ⏳ | ❌ |
|
|
|
|
---
|
|
|
|
## Known Issues
|
|
|
|
### Issue 1: Background Drop Zone Not Triggering
|
|
|
|
- **Symptom**: Dragging to empty space doesn't trigger root move
|
|
- **Likely cause**: `e.target === e.currentTarget` check may be wrong, or handlers not attached properly
|
|
- **Debug approach**: Add console.log to `handleBackgroundMouseEnter`
|
|
|
|
### Issue 2: Nested Component → Other Component Not Working
|
|
|
|
- **Symptom**: Can't drag a nested component to another component to create new nesting
|
|
- **Likely cause**: `canDrop` validation or drop handler not triggering
|
|
- **Debug approach**: Add console.log to `handleDrop` in ComponentItem
|
|
|
|
### Issue 3: Parent Folder → Component Not Working
|
|
|
|
- **Symptom**: Can't drag a folder with children onto a component
|
|
- **Likely cause**: Folder→Component case may not be recognized
|
|
- **Debug approach**: Check `handleDropOn` for folder→component case
|
|
|
|
### Issue 4: Component-Folder Drag Returns to Origin
|
|
|
|
- **Symptom**: Dragging component-folders snaps back instead of completing drop
|
|
- **Likely cause**: Missing `PopupLayer.endDrag()` call or wrong case branch
|
|
- **Debug approach**: Add logging to each case in `handleDropOn`
|
|
|
|
---
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 1: Diagnostic Logging (30 min)
|
|
|
|
Add comprehensive logging to understand current behavior:
|
|
|
|
```typescript
|
|
// In ComponentItem.tsx handleMouseEnter
|
|
console.log('🎯 Component hover:', { node, isDragging: PopupLayer.instance.isDragging() });
|
|
|
|
// In FolderItem.tsx handleMouseEnter
|
|
console.log('📁 Folder hover:', { folder, isDragging: PopupLayer.instance.isDragging() });
|
|
|
|
// In ComponentsPanelReact.tsx handleBackgroundMouseEnter
|
|
console.log('🏠 Background hover:', { target: e.target, currentTarget: e.currentTarget });
|
|
|
|
// In useComponentActions.ts handleDropOn
|
|
console.log('💾 handleDropOn called:', { draggedItem, targetItem });
|
|
|
|
// In useComponentActions.ts handleDropOnRoot
|
|
console.log('🏠 handleDropOnRoot called:', { draggedItem });
|
|
```
|
|
|
|
### Phase 2: Test Each Combination (1 hour)
|
|
|
|
Create test scenario for each combination and verify:
|
|
|
|
1. **B1**: Create `/CompA`, `/CompB`. Drag `/CompA` onto `/CompB`.
|
|
2. **B2**: Create `/CompA`, `/Folder`. Drag `/CompA` onto `/Folder`.
|
|
3. **B3**: Create `/Folder/CompA`. Drag `/CompA` to empty space.
|
|
4. **B4**: Create `/FolderA`, `/FolderB`. Drag `/FolderA` onto `/FolderB`.
|
|
5. **B5**: Create `/FolderA`, `/CompB`. Drag `/FolderA` onto `/CompB`.
|
|
6. **B6**: Create `/Parent/FolderA`. Drag `/FolderA` to empty space.
|
|
7. **B7**: Create `/CompParent` with nested `/CompParent/Child`. Drag `/CompParent` onto another component.
|
|
|
|
### Phase 3: Fix Issues (2-3 hours)
|
|
|
|
Address each failing combination based on diagnostic output.
|
|
|
|
### Phase 4: Remove Logging & Test (30 min)
|
|
|
|
Clean up debug code and verify all combinations work.
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
All items must pass:
|
|
|
|
- [ ] **B1**: Component → Component creates proper nesting
|
|
- [ ] **B2**: Component → Folder moves component into folder
|
|
- [ ] **B3**: Component → Root moves component to top level
|
|
- [ ] **B4**: Folder → Folder moves entire folder hierarchy
|
|
- [ ] **B5**: Folder → Component nests folder inside component
|
|
- [ ] **B6**: Folder → Root moves folder to top level
|
|
- [ ] **B7**: Component-Folder → any target works as folder
|
|
- [ ] **C1-C4**: All validations prevent invalid operations
|
|
- [ ] **D1-D4**: Visual feedback works for all scenarios
|
|
- [ ] **E1-E4**: Completion and undo/redo work
|
|
|
|
---
|
|
|
|
## Technical Notes
|
|
|
|
### PopupLayer Drag System Integration
|
|
|
|
The legacy `PopupLayer` uses a jQuery-based drag system:
|
|
|
|
```javascript
|
|
// Start drag
|
|
PopupLayer.instance.startDragging({
|
|
label: 'Item Name',
|
|
type: 'component' | 'folder',
|
|
dragTarget: HTMLElement,
|
|
onDragEnd: () => {
|
|
/* cleanup */
|
|
}
|
|
});
|
|
|
|
// During drag, from drop targets:
|
|
PopupLayer.instance.isDragging(); // Check if dragging
|
|
PopupLayer.instance.indicateDropType('move' | 'none'); // Visual feedback
|
|
|
|
// Complete drag
|
|
PopupLayer.instance.endDrag(); // Must be called for drop to complete!
|
|
```
|
|
|
|
**Critical**: If `endDrag()` is not called, the dragged element returns to origin.
|
|
|
|
### Component-Folder Pattern
|
|
|
|
When a component has nested children (e.g., `/Parent` with `/Parent/Child`), it's rendered as a `FolderItem` with attached component data:
|
|
|
|
```typescript
|
|
// In tree building:
|
|
{
|
|
type: 'folder',
|
|
data: {
|
|
path: '/Parent',
|
|
name: 'Parent',
|
|
isComponentFolder: true,
|
|
component: ComponentModel // The component at /Parent
|
|
}
|
|
}
|
|
```
|
|
|
|
Drop handlers must check `node.data.component` to handle these properly.
|
|
|
|
### Background Drop Zone
|
|
|
|
The background drop zone should trigger when:
|
|
|
|
1. User is dragging (PopupLayer.isDragging() === true)
|
|
2. Mouse enters the tree container
|
|
3. Mouse is NOT over any tree item (target === currentTarget)
|
|
|
|
The current implementation uses `e.target === e.currentTarget` which may be too restrictive.
|
|
|
|
---
|
|
|
|
## Files to Modify
|
|
|
|
1. **ComponentItem.tsx** - Add diagnostic logging, verify drop handlers
|
|
2. **FolderItem.tsx** - Add diagnostic logging, verify drop handlers
|
|
3. **ComponentsPanelReact.tsx** - Fix background drop zone
|
|
4. **useDragDrop.ts** - Verify canDrop logic
|
|
5. **useComponentActions.ts** - Verify all drop handler cases
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- **TASK-008 CHANGELOG** - Previous fix attempts documented
|
|
- **popuplayer.js** - Legacy drag system (don't modify, just understand)
|
|
- **UNDO-QUEUE-PATTERNS.md** - Correct undo patterns for operations
|
|
|
|
---
|
|
|
|
_Created: December 27, 2025_
|
|
_Last Updated: December 27, 2025_
|