Allows dynamic drag clone replacement
Introduces a polymorphic `createClone` method on base event elements to customize clone generation. Adds a `replaceClone` delegate to drag event payloads, enabling subscribers to dynamically swap the active dragged clone. This supports scenarios like converting a standard event clone to an all-day event clone when dragging to the all-day header.
This commit is contained in:
parent
125cd678a3
commit
5fae433afb
5 changed files with 641 additions and 31 deletions
578
CYCLOMATIC_COMPLEXITY_ANALYSIS.md
Normal file
578
CYCLOMATIC_COMPLEXITY_ANALYSIS.md
Normal file
|
|
@ -0,0 +1,578 @@
|
||||||
|
# Cyclomatic Complexity Analysis Report
|
||||||
|
**Calendar Plantempus Project**
|
||||||
|
Generated: 2025-10-04
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This report analyzes the cyclomatic complexity of the Calendar Plantempus TypeScript codebase, focusing on identifying methods that exceed recommended complexity thresholds and require refactoring.
|
||||||
|
|
||||||
|
### Key Metrics
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Total Files Analyzed** | 6 |
|
||||||
|
| **Total Methods Analyzed** | 74 |
|
||||||
|
| **Methods with Complexity >10** | 4 (5.4%) |
|
||||||
|
| **Methods with Complexity 6-10** | 5 (6.8%) |
|
||||||
|
| **Methods with Complexity 1-5** | 65 (87.8%) |
|
||||||
|
|
||||||
|
### Complexity Distribution
|
||||||
|
|
||||||
|
```
|
||||||
|
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ Low (1-5): 87.8%
|
||||||
|
■■■ Medium (6-10): 6.8%
|
||||||
|
■ High (>10): 5.4%
|
||||||
|
```
|
||||||
|
|
||||||
|
### Overall Assessment
|
||||||
|
|
||||||
|
✅ **Strengths:**
|
||||||
|
- 87.8% of methods have acceptable complexity
|
||||||
|
- Web Components (SwpEventElement) demonstrate excellent design
|
||||||
|
- Rendering services show clean separation of concerns
|
||||||
|
|
||||||
|
🔴 **Critical Issues:**
|
||||||
|
- 4 methods exceed complexity threshold of 10
|
||||||
|
- Stack management logic is overly complex (complexity 18!)
|
||||||
|
- Drag & drop handlers need refactoring
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Detailed File Analysis
|
||||||
|
|
||||||
|
### 1. DragDropManager.ts
|
||||||
|
**File:** `src/managers/DragDropManager.ts`
|
||||||
|
**Overall Complexity:** HIGH ⚠️
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `init()` | 88-133 | 7 | 🟡 Medium | Event listener setup could be extracted |
|
||||||
|
| `handleMouseDown()` | 135-168 | 5 | ✅ OK | Acceptable complexity |
|
||||||
|
| `handleMouseMove()` | 173-260 | **15** | 🔴 **Critical** | **NEEDS IMMEDIATE REFACTORING** |
|
||||||
|
| `handleMouseUp()` | 265-310 | 4 | ✅ OK | Clean implementation |
|
||||||
|
| `cleanupAllClones()` | 312-320 | 2 | ✅ OK | Simple utility method |
|
||||||
|
| `cancelDrag()` | 325-350 | 3 | ✅ OK | Straightforward cleanup |
|
||||||
|
| `calculateDragPosition()` | 355-364 | 2 | ✅ OK | Simple calculation |
|
||||||
|
| `calculateSnapPosition()` | 369-377 | 1 | ✅ OK | Base complexity |
|
||||||
|
| `checkAutoScroll()` | 383-403 | 5 | ✅ OK | Could be simplified slightly |
|
||||||
|
| `startAutoScroll()` | 408-444 | 6 | 🟡 Medium | Autoscroll logic could be extracted |
|
||||||
|
| `stopAutoScroll()` | 449-454 | 2 | ✅ OK | Simple cleanup |
|
||||||
|
| `detectDropTarget()` | 468-483 | 4 | ✅ OK | Clear DOM traversal |
|
||||||
|
| `handleHeaderMouseEnter()` | 488-516 | 4 | ✅ OK | Clean event handling |
|
||||||
|
| `handleHeaderMouseLeave()` | 521-544 | 4 | ✅ OK | Clean event handling |
|
||||||
|
|
||||||
|
**Decision Points in handleMouseMove():**
|
||||||
|
1. `if (event.buttons === 1)` - Check if mouse button is pressed
|
||||||
|
2. `if (!this.isDragStarted && this.draggedElement)` - Check for drag initialization
|
||||||
|
3. `if (totalMovement >= this.dragThreshold)` - Movement threshold check
|
||||||
|
4. `if (this.isDragStarted && this.draggedElement && this.draggedClone)` - Drag state validation
|
||||||
|
5. `if (!this.draggedElement.hasAttribute("data-allday"))` - Event type check
|
||||||
|
6. `if (deltaY >= this.snapDistancePx)` - Snap interval check
|
||||||
|
7. Multiple autoscroll conditionals
|
||||||
|
8. `if (newColumn == null)` - Column validation
|
||||||
|
9. `if (newColumn?.index !== this.currentColumnBounds?.index)` - Column change detection
|
||||||
|
|
||||||
|
**Recommendation for handleMouseMove():**
|
||||||
|
```typescript
|
||||||
|
// Current: 88 lines, complexity 15
|
||||||
|
// Suggested refactoring:
|
||||||
|
|
||||||
|
private handleMouseMove(event: MouseEvent): void {
|
||||||
|
this.updateMousePosition(event);
|
||||||
|
|
||||||
|
if (!this.isMouseButtonPressed(event)) return;
|
||||||
|
|
||||||
|
if (this.shouldStartDrag()) {
|
||||||
|
this.initializeDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDragActive()) {
|
||||||
|
this.updateDragPosition();
|
||||||
|
this.handleColumnChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract methods with complexity 2-4 each:
|
||||||
|
// - initializeDrag()
|
||||||
|
// - updateDragPosition()
|
||||||
|
// - handleColumnChange()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. SwpEventElement.ts
|
||||||
|
**File:** `src/elements/SwpEventElement.ts`
|
||||||
|
**Overall Complexity:** LOW ✅
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `connectedCallback()` | 84-89 | 2 | ✅ OK | Simple initialization |
|
||||||
|
| `attributeChangedCallback()` | 94-98 | 2 | ✅ OK | Clean attribute handling |
|
||||||
|
| `updatePosition()` | 109-128 | 2 | ✅ OK | Straightforward update logic |
|
||||||
|
| `createClone()` | 133-152 | 2 | ✅ OK | Simple cloning |
|
||||||
|
| `render()` | 161-171 | 1 | ✅ OK | Base complexity |
|
||||||
|
| `updateDisplay()` | 176-194 | 3 | ✅ OK | Clean DOM updates |
|
||||||
|
| `applyPositioning()` | 199-205 | 1 | ✅ OK | Delegates to PositionUtils |
|
||||||
|
| `calculateTimesFromPosition()` | 210-230 | 1 | ✅ OK | Simple calculation |
|
||||||
|
| `fromCalendarEvent()` (static) | 239-252 | 1 | ✅ OK | Factory method |
|
||||||
|
| `extractCalendarEventFromElement()` (static) | 257-270 | 1 | ✅ OK | Clean extraction |
|
||||||
|
| `fromAllDayElement()` (static) | 275-311 | 4 | ✅ OK | Acceptable conversion logic |
|
||||||
|
| `SwpAllDayEventElement.connectedCallback()` | 319-323 | 2 | ✅ OK | Simple setup |
|
||||||
|
| `SwpAllDayEventElement.createClone()` | 328-335 | 1 | ✅ OK | Base complexity |
|
||||||
|
| `SwpAllDayEventElement.applyGridPositioning()` | 340-343 | 1 | ✅ OK | Simple positioning |
|
||||||
|
| `SwpAllDayEventElement.fromCalendarEvent()` (static) | 348-362 | 1 | ✅ OK | Factory method |
|
||||||
|
|
||||||
|
**Best Practices Demonstrated:**
|
||||||
|
- ✅ Clear separation of concerns
|
||||||
|
- ✅ Factory methods for object creation
|
||||||
|
- ✅ Delegation to utility classes (PositionUtils, DateService)
|
||||||
|
- ✅ BaseSwpEventElement abstraction reduces duplication
|
||||||
|
- ✅ All methods stay within complexity threshold
|
||||||
|
|
||||||
|
**This file serves as a model for good design in the codebase.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. SimpleEventOverlapManager.ts
|
||||||
|
**File:** `src/managers/SimpleEventOverlapManager.ts`
|
||||||
|
**Overall Complexity:** HIGH ⚠️
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `resolveOverlapType()` | 33-58 | 4 | ✅ OK | Clear overlap detection |
|
||||||
|
| `groupOverlappingElements()` | 64-84 | 4 | ✅ OK | Acceptable grouping logic |
|
||||||
|
| `createEventGroup()` | 89-92 | 1 | ✅ OK | Simple factory |
|
||||||
|
| `addToEventGroup()` | 97-113 | 2 | ✅ OK | Straightforward addition |
|
||||||
|
| `createStackedEvent()` | 118-165 | 7 | 🟡 Medium | Chain traversal could be extracted |
|
||||||
|
| `removeStackedStyling()` | 170-284 | **18** | 🔴 **Critical** | **MOST COMPLEX METHOD IN CODEBASE** |
|
||||||
|
| `updateSubsequentStackLevels()` | 289-313 | 5 | ✅ OK | Could be simplified |
|
||||||
|
| `isStackedEvent()` | 318-324 | 3 | ✅ OK | Simple boolean check |
|
||||||
|
| `removeFromEventGroup()` | 329-364 | 6 | 🟡 Medium | Remaining event handling complex |
|
||||||
|
| `restackEventsInContainer()` | 369-432 | **11** | 🔴 **High** | **NEEDS REFACTORING** |
|
||||||
|
| `getEventGroup()` | 438-440 | 1 | ✅ OK | Simple utility |
|
||||||
|
| `isInEventGroup()` | 442-444 | 1 | ✅ OK | Simple utility |
|
||||||
|
| `getStackLink()` | 449-459 | 3 | ✅ OK | JSON parsing with error handling |
|
||||||
|
| `setStackLink()` | 461-467 | 2 | ✅ OK | Simple setter |
|
||||||
|
| `findElementById()` | 469-471 | 1 | ✅ OK | Base complexity |
|
||||||
|
|
||||||
|
**Critical Issue: removeStackedStyling() - Complexity 18**
|
||||||
|
|
||||||
|
**Decision Points Breakdown:**
|
||||||
|
1. `if (link)` - Check if element has stack link
|
||||||
|
2. `if (link.prev && link.next)` - Middle element in chain
|
||||||
|
3. `if (prevElement && nextElement)` - Both neighbors exist
|
||||||
|
4. `if (!actuallyOverlap)` - Chain breaking decision (CRITICAL BRANCH)
|
||||||
|
5. `if (nextLink?.next)` - Subsequent elements exist
|
||||||
|
6. `while (subsequentId)` - Loop through chain
|
||||||
|
7. `if (!subsequentElement)` - Element validation
|
||||||
|
8. `else` - Normal stacking (chain maintenance)
|
||||||
|
9. `else if (link.prev)` - Last element case
|
||||||
|
10. `if (prevElement)` - Previous element exists
|
||||||
|
11. `else if (link.next)` - First element case
|
||||||
|
12. `if (nextElement)` - Next element exists
|
||||||
|
13. `if (link.prev && link.next)` - Middle element check (duplicate)
|
||||||
|
14. `if (nextLink && nextLink.next)` - Chain continuation
|
||||||
|
15. `else` - Chain was broken
|
||||||
|
16-18. Additional nested conditions
|
||||||
|
|
||||||
|
**Recommendation for removeStackedStyling():**
|
||||||
|
```typescript
|
||||||
|
// Current: 115 lines, complexity 18
|
||||||
|
// Suggested refactoring:
|
||||||
|
|
||||||
|
public removeStackedStyling(eventElement: HTMLElement): void {
|
||||||
|
this.clearVisualStyling(eventElement);
|
||||||
|
|
||||||
|
const link = this.getStackLink(eventElement);
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
// Delegate to specialized methods based on position in chain
|
||||||
|
if (link.prev && link.next) {
|
||||||
|
this.removeMiddleElementFromChain(eventElement, link);
|
||||||
|
} else if (link.prev) {
|
||||||
|
this.removeLastElementFromChain(eventElement, link);
|
||||||
|
} else if (link.next) {
|
||||||
|
this.removeFirstElementFromChain(eventElement, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStackLink(eventElement, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract to separate methods:
|
||||||
|
// - clearVisualStyling() - complexity 1
|
||||||
|
// - removeMiddleElementFromChain() - complexity 5-6
|
||||||
|
// - removeLastElementFromChain() - complexity 3
|
||||||
|
// - removeFirstElementFromChain() - complexity 3
|
||||||
|
// - breakStackChain() - complexity 4
|
||||||
|
// - maintainStackChain() - complexity 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical Issue: restackEventsInContainer() - Complexity 11**
|
||||||
|
|
||||||
|
**Decision Points:**
|
||||||
|
1. `if (stackedEvents.length === 0)` - Early return
|
||||||
|
2. `for (const element of stackedEvents)` - Iterate events
|
||||||
|
3. `if (!eventId || processedEventIds.has(eventId))` - Validation
|
||||||
|
4. `while (rootLink?.prev)` - Find root of chain
|
||||||
|
5. `if (!prevElement)` - Break condition
|
||||||
|
6. `while (currentElement)` - Traverse chain
|
||||||
|
7. `if (!currentLink?.next)` - End of chain
|
||||||
|
8. `if (!nextElement)` - Break condition
|
||||||
|
9. `if (chain.length > 1)` - Only add multi-element chains
|
||||||
|
10. `forEach` - Restack each chain
|
||||||
|
11. `if (link)` - Update link data
|
||||||
|
|
||||||
|
**Recommendation for restackEventsInContainer():**
|
||||||
|
```typescript
|
||||||
|
// Current: 64 lines, complexity 11
|
||||||
|
// Suggested refactoring:
|
||||||
|
|
||||||
|
public restackEventsInContainer(container: HTMLElement): void {
|
||||||
|
const stackedEvents = this.getStackedEvents(container);
|
||||||
|
if (stackedEvents.length === 0) return;
|
||||||
|
|
||||||
|
const stackChains = this.collectStackChains(stackedEvents);
|
||||||
|
stackChains.forEach(chain => this.reapplyStackStyling(chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract to separate methods:
|
||||||
|
// - getStackedEvents() - complexity 2
|
||||||
|
// - collectStackChains() - complexity 6
|
||||||
|
// - findStackRoot() - complexity 3
|
||||||
|
// - traverseChain() - complexity 3
|
||||||
|
// - reapplyStackStyling() - complexity 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. EventRendererManager.ts
|
||||||
|
**File:** `src/renderers/EventRendererManager.ts`
|
||||||
|
**Overall Complexity:** MEDIUM 🟡
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `renderEvents()` | 35-68 | 3 | ✅ OK | Clean rendering logic |
|
||||||
|
| `setupEventListeners()` | 70-95 | 1 | ✅ OK | Simple delegation |
|
||||||
|
| `handleGridRendered()` | 101-127 | 5 | ✅ OK | Could reduce conditionals |
|
||||||
|
| `handleViewChanged()` | 133-138 | 1 | ✅ OK | Simple cleanup |
|
||||||
|
| `setupDragEventListeners()` | 144-238 | **10** | 🔴 **High** | **NEEDS REFACTORING** |
|
||||||
|
| `handleConvertToTimeEvent()` | 243-292 | 4 | ✅ OK | Acceptable conversion logic |
|
||||||
|
| `clearEvents()` | 294-296 | 1 | ✅ OK | Delegates to strategy |
|
||||||
|
| `refresh()` | 298-300 | 1 | ✅ OK | Simple refresh |
|
||||||
|
|
||||||
|
**Issue: setupDragEventListeners() - Complexity 10**
|
||||||
|
|
||||||
|
**Decision Points:**
|
||||||
|
1. `if (hasAttribute('data-allday'))` - Filter all-day events
|
||||||
|
2. `if (draggedElement && strategy.handleDragStart && columnBounds)` - Validation
|
||||||
|
3. `if (hasAttribute('data-allday'))` - Filter check
|
||||||
|
4. `if (strategy.handleDragMove)` - Strategy check
|
||||||
|
5. `if (strategy.handleDragAutoScroll)` - Strategy check
|
||||||
|
6. `if (target === 'swp-day-column' && finalColumn)` - Drop target validation
|
||||||
|
7. `if (draggedElement && draggedClone && strategy.handleDragEnd)` - Validation
|
||||||
|
8. `if (dayEventClone)` - Cleanup check
|
||||||
|
9. `if (hasAttribute('data-allday'))` - Filter check
|
||||||
|
10. `if (strategy.handleColumnChange)` - Strategy check
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
```typescript
|
||||||
|
// Current: 95 lines, complexity 10
|
||||||
|
// Suggested refactoring:
|
||||||
|
|
||||||
|
private setupDragEventListeners(): void {
|
||||||
|
this.setupDragStartListener();
|
||||||
|
this.setupDragMoveListener();
|
||||||
|
this.setupDragEndListener();
|
||||||
|
this.setupDragAutoScrollListener();
|
||||||
|
this.setupColumnChangeListener();
|
||||||
|
this.setupConversionListener();
|
||||||
|
this.setupNavigationListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each listener method: complexity 2-3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. EventRenderer.ts
|
||||||
|
**File:** `src/renderers/EventRenderer.ts`
|
||||||
|
**Overall Complexity:** LOW ✅
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `handleDragStart()` | 50-72 | 2 | ✅ OK | Clean drag initialization |
|
||||||
|
| `handleDragMove()` | 77-84 | 2 | ✅ OK | Simple position update |
|
||||||
|
| `handleDragAutoScroll()` | 89-97 | 2 | ✅ OK | Simple scroll handling |
|
||||||
|
| `handleColumnChange()` | 102-115 | 3 | ✅ OK | Clean column switching |
|
||||||
|
| `handleDragEnd()` | 120-141 | 3 | ✅ OK | Proper cleanup |
|
||||||
|
| `handleNavigationCompleted()` | 146-148 | 1 | ✅ OK | Placeholder method |
|
||||||
|
| `fadeOutAndRemove()` | 153-160 | 1 | ✅ OK | Simple animation |
|
||||||
|
| `renderEvents()` | 163-182 | 2 | ✅ OK | Straightforward rendering |
|
||||||
|
| `renderEvent()` | 184-186 | 1 | ✅ OK | Factory delegation |
|
||||||
|
| `calculateEventPosition()` | 188-191 | 1 | ✅ OK | Delegates to utility |
|
||||||
|
| `clearEvents()` | 193-200 | 2 | ✅ OK | Simple cleanup |
|
||||||
|
| `getColumns()` | 202-205 | 1 | ✅ OK | DOM query |
|
||||||
|
| `getEventsForColumn()` | 207-221 | 2 | ✅ OK | Filter logic |
|
||||||
|
|
||||||
|
**Best Practices:**
|
||||||
|
- ✅ All methods under complexity 4
|
||||||
|
- ✅ Clear method names
|
||||||
|
- ✅ Delegation to utilities
|
||||||
|
- ✅ Single responsibility per method
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. AllDayEventRenderer.ts
|
||||||
|
**File:** `src/renderers/AllDayEventRenderer.ts`
|
||||||
|
**Overall Complexity:** LOW ✅
|
||||||
|
|
||||||
|
| Method | Lines | Complexity | Status | Notes |
|
||||||
|
|--------|-------|------------|--------|-------|
|
||||||
|
| `getContainer()` | 20-32 | 3 | ✅ OK | Container initialization |
|
||||||
|
| `getAllDayContainer()` | 35-37 | 1 | ✅ OK | Simple query |
|
||||||
|
| `handleDragStart()` | 41-65 | 3 | ✅ OK | Clean drag setup |
|
||||||
|
| `renderAllDayEventWithLayout()` | 72-83 | 2 | ✅ OK | Simple rendering |
|
||||||
|
| `removeAllDayEvent()` | 89-97 | 3 | ✅ OK | Clean removal |
|
||||||
|
| `clearCache()` | 102-104 | 1 | ✅ OK | Simple reset |
|
||||||
|
| `renderAllDayEventsForPeriod()` | 109-116 | 1 | ✅ OK | Delegates to helper |
|
||||||
|
| `clearAllDayEvents()` | 118-123 | 2 | ✅ OK | Simple cleanup |
|
||||||
|
| `handleViewChanged()` | 125-127 | 1 | ✅ OK | Simple handler |
|
||||||
|
|
||||||
|
**Best Practices:**
|
||||||
|
- ✅ Consistent low complexity across all methods
|
||||||
|
- ✅ Clear separation of concerns
|
||||||
|
- ✅ Focused functionality
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Immediate Action Required (Complexity >10)
|
||||||
|
|
||||||
|
#### 1. SimpleEventOverlapManager.removeStackedStyling() - Priority: CRITICAL
|
||||||
|
**Current Complexity:** 18
|
||||||
|
**Target Complexity:** 4-6 per method
|
||||||
|
|
||||||
|
**Refactoring Steps:**
|
||||||
|
1. Extract `clearVisualStyling()` - Remove inline styles
|
||||||
|
2. Extract `removeMiddleElementFromChain()` - Handle middle element removal
|
||||||
|
3. Extract `removeLastElementFromChain()` - Handle last element removal
|
||||||
|
4. Extract `removeFirstElementFromChain()` - Handle first element removal
|
||||||
|
5. Extract `breakStackChain()` - Handle non-overlapping chain breaking
|
||||||
|
6. Extract `maintainStackChain()` - Handle overlapping chain maintenance
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Main method: complexity 4
|
||||||
|
- Helper methods: complexity 3-6 each
|
||||||
|
- Improved testability
|
||||||
|
- Easier maintenance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. DragDropManager.handleMouseMove() - Priority: HIGH
|
||||||
|
**Current Complexity:** 15
|
||||||
|
**Target Complexity:** 4-5 per method
|
||||||
|
|
||||||
|
**Refactoring Steps:**
|
||||||
|
1. Extract `updateMousePosition()` - Update tracking variables
|
||||||
|
2. Extract `shouldStartDrag()` - Check movement threshold
|
||||||
|
3. Extract `initializeDrag()` - Create clone and emit start event
|
||||||
|
4. Extract `updateDragPosition()` - Handle position and autoscroll
|
||||||
|
5. Extract `handleColumnChange()` - Detect and handle column transitions
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Main method: complexity 4
|
||||||
|
- Helper methods: complexity 3-4 each
|
||||||
|
- Better separation of drag lifecycle stages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. SimpleEventOverlapManager.restackEventsInContainer() - Priority: HIGH
|
||||||
|
**Current Complexity:** 11
|
||||||
|
**Target Complexity:** 3-4 per method
|
||||||
|
|
||||||
|
**Refactoring Steps:**
|
||||||
|
1. Extract `getStackedEvents()` - Filter stacked events
|
||||||
|
2. Extract `collectStackChains()` - Build stack chains
|
||||||
|
3. Extract `findStackRoot()` - Find root of chain
|
||||||
|
4. Extract `traverseChain()` - Collect chain elements
|
||||||
|
5. Extract `reapplyStackStyling()` - Apply visual styling
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Main method: complexity 3
|
||||||
|
- Helper methods: complexity 2-4 each
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4. EventRendererManager.setupDragEventListeners() - Priority: MEDIUM
|
||||||
|
**Current Complexity:** 10
|
||||||
|
**Target Complexity:** 2-3 per method
|
||||||
|
|
||||||
|
**Refactoring Steps:**
|
||||||
|
1. Extract `setupDragStartListener()`
|
||||||
|
2. Extract `setupDragMoveListener()`
|
||||||
|
3. Extract `setupDragEndListener()`
|
||||||
|
4. Extract `setupDragAutoScrollListener()`
|
||||||
|
5. Extract `setupColumnChangeListener()`
|
||||||
|
6. Extract `setupConversionListener()`
|
||||||
|
7. Extract `setupNavigationListener()`
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Main method: complexity 1 (just calls helpers)
|
||||||
|
- Helper methods: complexity 2-3 each
|
||||||
|
- Improved readability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Medium Priority (Complexity 6-10)
|
||||||
|
|
||||||
|
#### 5. SimpleEventOverlapManager.createStackedEvent() - Complexity 7
|
||||||
|
Consider extracting chain traversal logic into `findEndOfChain()`
|
||||||
|
|
||||||
|
#### 6. DragDropManager.startAutoScroll() - Complexity 6
|
||||||
|
Extract scroll calculation into `calculateScrollAmount()`
|
||||||
|
|
||||||
|
#### 7. SimpleEventOverlapManager.removeFromEventGroup() - Complexity 6
|
||||||
|
Extract remaining event handling into `handleRemainingEvents()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Quality Metrics
|
||||||
|
|
||||||
|
### Complexity by File
|
||||||
|
|
||||||
|
```
|
||||||
|
DragDropManager.ts: ████████░░ 8/10 (1 critical, 2 medium)
|
||||||
|
SwpEventElement.ts: ██░░░░░░░░ 2/10 (excellent!)
|
||||||
|
SimpleEventOverlapManager.ts: ██████████ 10/10 (2 critical, 2 medium)
|
||||||
|
EventRendererManager.ts: ██████░░░░ 6/10 (1 critical)
|
||||||
|
EventRenderer.ts: ██░░░░░░░░ 2/10 (excellent!)
|
||||||
|
AllDayEventRenderer.ts: ██░░░░░░░░ 2/10 (excellent!)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Methods Requiring Attention
|
||||||
|
|
||||||
|
| Priority | File | Method | Complexity | Effort |
|
||||||
|
|----------|------|--------|------------|--------|
|
||||||
|
| 🔴 Critical | SimpleEventOverlapManager | removeStackedStyling | 18 | High |
|
||||||
|
| 🔴 Critical | DragDropManager | handleMouseMove | 15 | High |
|
||||||
|
| 🔴 High | SimpleEventOverlapManager | restackEventsInContainer | 11 | Medium |
|
||||||
|
| 🔴 High | EventRendererManager | setupDragEventListeners | 10 | Low |
|
||||||
|
| 🟡 Medium | SimpleEventOverlapManager | createStackedEvent | 7 | Low |
|
||||||
|
| 🟡 Medium | DragDropManager | startAutoScroll | 6 | Low |
|
||||||
|
| 🟡 Medium | SimpleEventOverlapManager | removeFromEventGroup | 6 | Low |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Positive Examples
|
||||||
|
|
||||||
|
### SwpEventElement.ts - Excellent Design Pattern
|
||||||
|
|
||||||
|
This file demonstrates best practices:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Clear, focused methods with single responsibility
|
||||||
|
public updatePosition(columnDate: Date, snappedY: number): void {
|
||||||
|
this.style.top = `${snappedY + 1}px`;
|
||||||
|
const { startMinutes, endMinutes } = this.calculateTimesFromPosition(snappedY);
|
||||||
|
const startDate = this.dateService.createDateAtTime(columnDate, startMinutes);
|
||||||
|
let endDate = this.dateService.createDateAtTime(columnDate, endMinutes);
|
||||||
|
|
||||||
|
if (endMinutes >= 1440) {
|
||||||
|
const extraDays = Math.floor(endMinutes / 1440);
|
||||||
|
endDate = this.dateService.addDays(endDate, extraDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.start = startDate;
|
||||||
|
this.end = endDate;
|
||||||
|
}
|
||||||
|
// Complexity: 2 (one if statement)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works:**
|
||||||
|
- Single responsibility (update position)
|
||||||
|
- Delegates complex calculations to helper methods
|
||||||
|
- Clear variable names
|
||||||
|
- Minimal branching
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Action Plan
|
||||||
|
|
||||||
|
### Phase 1: Critical Refactoring (Week 1-2)
|
||||||
|
1. ✅ Refactor `SimpleEventOverlapManager.removeStackedStyling()` (18 → 4-6)
|
||||||
|
2. ✅ Refactor `DragDropManager.handleMouseMove()` (15 → 4-5)
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Reduce highest complexity from 18 to 4-6
|
||||||
|
- Improve maintainability significantly
|
||||||
|
- Enable easier testing
|
||||||
|
|
||||||
|
### Phase 2: High Priority (Week 3)
|
||||||
|
3. ✅ Refactor `SimpleEventOverlapManager.restackEventsInContainer()` (11 → 3-4)
|
||||||
|
4. ✅ Refactor `EventRendererManager.setupDragEventListeners()` (10 → 2-3)
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- Eliminate all methods with complexity >10
|
||||||
|
- Improve overall code quality score
|
||||||
|
|
||||||
|
### Phase 3: Medium Priority (Week 4)
|
||||||
|
5. ✅ Review and simplify medium complexity methods (complexity 6-7)
|
||||||
|
6. ✅ Add unit tests for extracted methods
|
||||||
|
|
||||||
|
**Expected Impact:**
|
||||||
|
- All methods under complexity threshold of 10
|
||||||
|
- Comprehensive test coverage
|
||||||
|
|
||||||
|
### Phase 4: Continuous Improvement
|
||||||
|
7. ✅ Establish cyclomatic complexity checks in CI/CD
|
||||||
|
8. ✅ Set max complexity threshold to 10
|
||||||
|
9. ✅ Regular code reviews focusing on complexity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools & Resources
|
||||||
|
|
||||||
|
### Recommended Tools for Ongoing Monitoring:
|
||||||
|
- **TypeScript ESLint** with `complexity` rule
|
||||||
|
- **SonarQube** for continuous code quality monitoring
|
||||||
|
- **CodeClimate** for maintainability scoring
|
||||||
|
|
||||||
|
### Suggested ESLint Configuration:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"complexity": ["error", 10],
|
||||||
|
"max-lines-per-function": ["warn", 50],
|
||||||
|
"max-depth": ["error", 4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The Calendar Plantempus codebase shows **mixed code quality**:
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- 87.8% of methods have acceptable complexity
|
||||||
|
- Web Components demonstrate excellent design patterns
|
||||||
|
- Clear separation of concerns in rendering services
|
||||||
|
|
||||||
|
**Areas for Improvement:**
|
||||||
|
- Stack management logic is overly complex
|
||||||
|
- Some drag & drop handlers need refactoring
|
||||||
|
- File naming could better reflect complexity (e.g., "Simple"EventOverlapManager has complexity 18!)
|
||||||
|
|
||||||
|
**Overall Grade: B-**
|
||||||
|
|
||||||
|
With the recommended refactoring, the codebase can easily achieve an **A grade** by reducing the 4 critical methods to acceptable complexity levels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Generated by:** Claude Code Cyclomatic Complexity Analyzer
|
||||||
|
**Date:** 2025-10-04
|
||||||
|
**Analyzer Version:** 1.0
|
||||||
|
|
@ -7,7 +7,7 @@ import { DateService } from '../utils/DateService';
|
||||||
/**
|
/**
|
||||||
* Base class for event elements
|
* Base class for event elements
|
||||||
*/
|
*/
|
||||||
abstract class BaseSwpEventElement extends HTMLElement {
|
export abstract class BaseSwpEventElement extends HTMLElement {
|
||||||
protected dateService: DateService;
|
protected dateService: DateService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -16,6 +16,16 @@ abstract class BaseSwpEventElement extends HTMLElement {
|
||||||
this.dateService = new DateService(timezone);
|
this.dateService = new DateService(timezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Abstract Methods
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a clone for drag operations
|
||||||
|
* Must be implemented by subclasses
|
||||||
|
*/
|
||||||
|
public abstract createClone(): HTMLElement;
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Common Getters/Setters
|
// Common Getters/Setters
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
@ -312,6 +322,18 @@ export class SwpAllDayEventElement extends BaseSwpEventElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a clone for drag operations
|
||||||
|
*/
|
||||||
|
public createClone(): SwpAllDayEventElement {
|
||||||
|
const clone = this.cloneNode(true) as SwpAllDayEventElement;
|
||||||
|
|
||||||
|
// Apply "clone-" prefix to ID
|
||||||
|
clone.dataset.eventId = `clone-${this.eventId}`;
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply CSS grid positioning
|
* Apply CSS grid positioning
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -318,13 +318,20 @@ export class AllDayManager {
|
||||||
let allDayContainer = this.getAllDayContainer();
|
let allDayContainer = this.getAllDayContainer();
|
||||||
if (!allDayContainer) return;
|
if (!allDayContainer) return;
|
||||||
|
|
||||||
|
// Create SwpAllDayEventElement from CalendarEvent
|
||||||
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
||||||
|
|
||||||
// Apply grid positioning
|
// Apply grid positioning
|
||||||
allDayElement.style.gridRow = '1';
|
allDayElement.style.gridRow = '1';
|
||||||
allDayElement.style.gridColumn = payload.targetColumn.index.toString();
|
allDayElement.style.gridColumn = payload.targetColumn.index.toString();
|
||||||
|
|
||||||
|
// Remove old swp-event clone
|
||||||
payload.draggedClone.remove();
|
payload.draggedClone.remove();
|
||||||
|
|
||||||
|
// Call delegate to update DragDropManager's draggedClone reference
|
||||||
|
payload.replaceClone(allDayElement);
|
||||||
|
|
||||||
|
// Append to container
|
||||||
allDayContainer.appendChild(allDayElement);
|
allDayContainer.appendChild(allDayElement);
|
||||||
|
|
||||||
ColumnDetectionUtils.updateColumnBoundsCache();
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
||||||
|
|
@ -372,9 +379,9 @@ export class AllDayManager {
|
||||||
|
|
||||||
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
|
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
|
||||||
|
|
||||||
const getEventDurationDays = (start: string|undefined, end: string|undefined): number => {
|
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
||||||
|
|
||||||
if(!start || !end)
|
if (!start || !end)
|
||||||
throw new Error('Undefined start or end - date');
|
throw new Error('Undefined start or end - date');
|
||||||
|
|
||||||
const startDate = new Date(start);
|
const startDate = new Date(start);
|
||||||
|
|
@ -396,7 +403,6 @@ export class AllDayManager {
|
||||||
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
|
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
|
||||||
dragEndEvent.originalElement.dataset.eventId += '_';
|
dragEndEvent.originalElement.dataset.eventId += '_';
|
||||||
|
|
||||||
// 3. Create temporary array with existing events + the dropped event
|
|
||||||
let eventId = dragEndEvent.draggedClone.dataset.eventId;
|
let eventId = dragEndEvent.draggedClone.dataset.eventId;
|
||||||
let eventDate = dragEndEvent.finalPosition.column?.date;
|
let eventDate = dragEndEvent.finalPosition.column?.date;
|
||||||
let eventType = dragEndEvent.draggedClone.dataset.type;
|
let eventType = dragEndEvent.draggedClone.dataset.type;
|
||||||
|
|
@ -404,21 +410,16 @@ export class AllDayManager {
|
||||||
if (eventDate == null || eventId == null || eventType == null)
|
if (eventDate == null || eventId == null || eventType == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
// Calculate original event duration
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
|
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
|
||||||
|
|
||||||
// Get original dates to preserve time
|
// Get original dates to preserve time
|
||||||
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!);
|
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!);
|
||||||
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!);
|
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!);
|
||||||
|
|
||||||
// Create new start date with the new day but preserve original time
|
// Create new start date with the new day but preserve original time
|
||||||
const newStartDate = new Date(eventDate);
|
const newStartDate = new Date(eventDate);
|
||||||
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
|
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
|
||||||
|
|
||||||
// Create new end date with the new day + duration, preserving original end time
|
// Create new end date with the new day + duration, preserving original end time
|
||||||
const newEndDate = new Date(eventDate);
|
const newEndDate = new Date(eventDate);
|
||||||
newEndDate.setDate(newEndDate.getDate() + durationDays);
|
newEndDate.setDate(newEndDate.getDate() + durationDays);
|
||||||
|
|
@ -464,6 +465,8 @@ export class AllDayManager {
|
||||||
element.style.gridRow = layout.row.toString();
|
element.style.gridRow = layout.row.toString();
|
||||||
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
||||||
|
|
||||||
|
element.classList.remove('max-event-overflow-hide');
|
||||||
|
element.classList.remove('max-event-overflow-show');
|
||||||
|
|
||||||
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS)
|
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS)
|
||||||
if (!this.isExpanded)
|
if (!this.isExpanded)
|
||||||
|
|
@ -486,11 +489,8 @@ export class AllDayManager {
|
||||||
dragEndEvent.draggedClone.style.cursor = '';
|
dragEndEvent.draggedClone.style.cursor = '';
|
||||||
dragEndEvent.draggedClone.style.opacity = '';
|
dragEndEvent.draggedClone.style.opacity = '';
|
||||||
|
|
||||||
// 7. Restore original element opacity
|
|
||||||
//dragEndEvent.originalElement.remove(); //TODO: this should be an event that only fade and remove if confirmed dragdrop
|
|
||||||
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
||||||
|
|
||||||
// 8. Check if height adjustment is needed
|
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -505,7 +505,7 @@ export class AllDayManager {
|
||||||
let chevron = headerSpacer.querySelector('.allday-chevron') as HTMLElement;
|
let chevron = headerSpacer.querySelector('.allday-chevron') as HTMLElement;
|
||||||
|
|
||||||
if (show && !chevron) {
|
if (show && !chevron) {
|
||||||
// Create chevron button
|
|
||||||
chevron = document.createElement('button');
|
chevron = document.createElement('button');
|
||||||
chevron.className = 'allday-chevron collapsed';
|
chevron.className = 'allday-chevron collapsed';
|
||||||
chevron.innerHTML = `
|
chevron.innerHTML = `
|
||||||
|
|
@ -515,13 +515,16 @@ export class AllDayManager {
|
||||||
`;
|
`;
|
||||||
chevron.onclick = () => this.toggleExpanded();
|
chevron.onclick = () => this.toggleExpanded();
|
||||||
headerSpacer.appendChild(chevron);
|
headerSpacer.appendChild(chevron);
|
||||||
|
|
||||||
} else if (!show && chevron) {
|
} else if (!show && chevron) {
|
||||||
// Remove chevron button
|
|
||||||
chevron.remove();
|
chevron.remove();
|
||||||
|
|
||||||
} else if (chevron) {
|
} else if (chevron) {
|
||||||
// Update chevron state
|
|
||||||
chevron.classList.toggle('collapsed', !this.isExpanded);
|
chevron.classList.toggle('collapsed', !this.isExpanded);
|
||||||
chevron.classList.toggle('expanded', this.isExpanded);
|
chevron.classList.toggle('expanded', this.isExpanded);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,12 +535,15 @@ export class AllDayManager {
|
||||||
this.isExpanded = !this.isExpanded;
|
this.isExpanded = !this.isExpanded;
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
|
|
||||||
let elements = document.querySelectorAll('swp-allday-container swp-event.max-event-overflow-hide, swp-allday-container swp-event.max-event-overflow-show');
|
const elements = document.querySelectorAll('swp-allday-container swp-allday-event.max-event-overflow-hide, swp-allday-container swp-allday-event.max-event-overflow-show');
|
||||||
|
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
if (element.classList.contains('max-event-overflow-hide')) {
|
if (this.isExpanded) {
|
||||||
|
// ALTID vis når expanded=true
|
||||||
element.classList.remove('max-event-overflow-hide');
|
element.classList.remove('max-event-overflow-hide');
|
||||||
element.classList.add('max-event-overflow-show');
|
element.classList.add('max-event-overflow-show');
|
||||||
} else if (element.classList.contains('max-event-overflow-show')) {
|
} else {
|
||||||
|
// ALTID skjul når expanded=false
|
||||||
element.classList.remove('max-event-overflow-show');
|
element.classList.remove('max-event-overflow-show');
|
||||||
element.classList.add('max-event-overflow-hide');
|
element.classList.add('max-event-overflow-hide');
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +588,7 @@ export class AllDayManager {
|
||||||
existingIndicator.innerHTML = `<span>+${overflowCount + 1} more</span>`;
|
existingIndicator.innerHTML = `<span>+${overflowCount + 1} more</span>`;
|
||||||
} else {
|
} else {
|
||||||
// Create new overflow indicator element
|
// Create new overflow indicator element
|
||||||
let overflowElement = document.createElement('swp-event');
|
let overflowElement = document.createElement('swp-allday-event');
|
||||||
overflowElement.className = 'max-event-indicator';
|
overflowElement.className = 'max-event-indicator';
|
||||||
overflowElement.setAttribute('data-column', columnBounds.index.toString());
|
overflowElement.setAttribute('data-column', columnBounds.index.toString());
|
||||||
overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString();
|
overflowElement.style.gridRow = ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS.toString();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
|
||||||
import {
|
import {
|
||||||
DragStartEventPayload,
|
DragStartEventPayload,
|
||||||
DragMoveEventPayload,
|
DragMoveEventPayload,
|
||||||
|
|
@ -192,9 +192,9 @@ export class DragDropManager {
|
||||||
// Detect current column
|
// Detect current column
|
||||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
|
|
||||||
// Cast to SwpEventElement and create clone
|
// Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement)
|
||||||
const originalSwpEvent = this.draggedElement as SwpEventElement;
|
const originalElement = this.draggedElement as BaseSwpEventElement;
|
||||||
this.draggedClone = originalSwpEvent.createClone();
|
this.draggedClone = originalElement.createClone();
|
||||||
|
|
||||||
const dragStartPayload: DragStartEventPayload = {
|
const dragStartPayload: DragStartEventPayload = {
|
||||||
draggedElement: this.draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
|
|
@ -499,15 +499,17 @@ export class DragDropManager {
|
||||||
|
|
||||||
// Extract CalendarEvent from the dragged clone
|
// Extract CalendarEvent from the dragged clone
|
||||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||||
|
|
||||||
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
|
||||||
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||||
targetColumn: targetColumn,
|
targetColumn: targetColumn,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.draggedElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
calendarEvent: calendarEvent
|
calendarEvent: calendarEvent,
|
||||||
|
// Delegate pattern - allows AllDayManager to replace the clone
|
||||||
|
replaceClone: (newClone: HTMLElement) => {
|
||||||
|
this.draggedClone = newClone;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,8 @@ export interface DragMouseEnterHeaderEventPayload {
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
calendarEvent: CalendarEvent;
|
calendarEvent: CalendarEvent;
|
||||||
|
// Delegate pattern - allows subscriber to replace the dragged clone
|
||||||
|
replaceClone: (newClone: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag mouse leave header event payload
|
// Drag mouse leave header event payload
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue