feat: implement framework and refactor everything

This commit is contained in:
2025-08-26 20:35:15 +02:00
parent e23b40e0cb
commit 918c019dd5
23 changed files with 6291 additions and 1279 deletions

266
docs/STATE_PRESERVATION.md Normal file
View File

@@ -0,0 +1,266 @@
# SPORE UI State Preservation System
## Overview
The SPORE UI framework now includes an advanced state preservation system that prevents UI state loss during data refreshes. This system ensures that user interactions like expanded cards, active tabs, and other UI state are maintained when data is updated from the server.
## Key Features
### 1. **UI State Persistence**
- **Expanded Cards**: When cluster member cards are expanded, their state is preserved across data refreshes
- **Active Tabs**: Active tab selections within node detail views are maintained
- **User Interactions**: All user-initiated UI changes are stored and restored automatically
### 2. **Smart Data Updates**
- **Change Detection**: The system detects when data has actually changed and only updates what's necessary
- **Partial Updates**: Components can update specific data without re-rendering the entire UI
- **State Preservation**: UI state is automatically preserved during all data operations
### 3. **Efficient Rendering**
- **No Full Re-renders**: Components avoid unnecessary full re-renders when only data changes
- **Granular Updates**: Only changed properties trigger UI updates
- **Performance Optimization**: Reduced DOM manipulation and improved user experience
## Architecture
### Enhanced ViewModel Class
The base `ViewModel` class now includes:
```javascript
class ViewModel {
// UI State Management
setUIState(key, value) // Store UI state
getUIState(key) // Retrieve UI state
getAllUIState() // Get all stored UI state
clearUIState(key) // Clear specific or all UI state
// Change Detection
hasChanged(property) // Check if property changed
getPrevious(property) // Get previous value
// Batch Updates
batchUpdate(updates, options) // Update multiple properties with state preservation
}
```
### Enhanced Component Class
The base `Component` class now includes:
```javascript
class Component {
// UI State Management
setUIState(key, value) // Store local UI state
getUIState(key) // Get local or view model state
getAllUIState() // Get merged state
restoreUIState() // Restore state from view model
// Partial Updates
updatePartial(property, newValue, previousValue) // Handle partial updates
}
```
## Implementation Examples
### 1. **Cluster Members Component**
The `ClusterMembersComponent` demonstrates state preservation:
```javascript
class ClusterMembersComponent extends Component {
setupViewModelListeners() {
// Listen with change detection
this.subscribeToProperty('members', this.handleMembersUpdate.bind(this));
}
handleMembersUpdate(newMembers, previousMembers) {
if (this.shouldPreserveState(newMembers, previousMembers)) {
// Partial update preserves UI state
this.updateMembersPartially(newMembers, previousMembers);
} else {
// Full re-render only when necessary
this.render();
}
}
shouldPreserveState(newMembers, previousMembers) {
// Check if member structure allows state preservation
if (newMembers.length !== previousMembers.length) return false;
const newIps = new Set(newMembers.map(m => m.ip));
const prevIps = new Set(previousMembers.map(m => m.ip));
return newIps.size === prevIps.size &&
[...newIps].every(ip => prevIps.has(ip));
}
}
```
### 2. **Node Details Component**
The `NodeDetailsComponent` preserves active tab state:
```javascript
class NodeDetailsComponent extends Component {
setupViewModelListeners() {
this.subscribeToProperty('activeTab', this.handleActiveTabUpdate.bind(this));
}
handleActiveTabUpdate(newTab, previousTab) {
// Update tab UI without full re-render
this.updateActiveTab(newTab, previousTab);
}
updateActiveTab(newTab) {
// Update only the tab UI, preserving other state
const tabButtons = this.findAllElements('.tab-button');
const tabContents = this.findAllElements('.tab-content');
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
const activeButton = this.findElement(`[data-tab="${newTab}"]`);
const activeContent = this.findElement(`#${newTab}-tab`);
if (activeButton) activeButton.classList.add('active');
if (activeContent) activeContent.classList.add('active');
}
}
```
## Usage Patterns
### 1. **Storing UI State**
```javascript
// In a component
this.setUIState('expandedCard', memberIp);
this.setUIState('activeTab', 'firmware');
// In a view model
this.setUIState('userPreferences', { theme: 'dark', layout: 'compact' });
```
### 2. **Retrieving UI State**
```javascript
// Get specific state
const expandedCard = this.getUIState('expandedCard');
const activeTab = this.getUIState('activeTab');
// Get all state
const allState = this.getAllUIState();
```
### 3. **Batch Updates with State Preservation**
```javascript
// Update data while preserving UI state
this.viewModel.batchUpdate({
members: newMembers,
lastUpdateTime: new Date().toISOString()
}, { preserveUIState: true });
```
### 4. **Smart Updates**
```javascript
// Use smart update to preserve state
await this.viewModel.smartUpdate();
```
## Benefits
### 1. **Improved User Experience**
- Users don't lose their place in the interface
- Expanded cards remain expanded
- Active tabs stay selected
- No jarring UI resets
### 2. **Better Performance**
- Reduced unnecessary DOM manipulation
- Efficient partial updates
- Optimized rendering cycles
### 3. **Maintainable Code**
- Clear separation of concerns
- Consistent state management patterns
- Easy to extend and modify
## Testing
Use the `test-state-preservation.html` file to test the state preservation system:
1. **Expand cluster member cards**
2. **Change active tabs in node details**
3. **Trigger data refresh**
4. **Verify state is preserved**
## Migration Guide
### From Old System
If you're upgrading from the old system:
1. **Update ViewModel Listeners**: Change from `this.render.bind(this)` to specific update handlers
2. **Add State Management**: Use `setUIState()` and `getUIState()` for UI state
3. **Implement Partial Updates**: Override `updatePartial()` method for efficient updates
4. **Use Smart Updates**: Replace direct data updates with `smartUpdate()` calls
### Example Migration
**Old Code:**
```javascript
this.subscribeToProperty('members', this.render.bind(this));
async handleRefresh() {
await this.viewModel.updateClusterMembers();
}
```
**New Code:**
```javascript
this.subscribeToProperty('members', this.handleMembersUpdate.bind(this));
async handleRefresh() {
await this.viewModel.smartUpdate();
}
handleMembersUpdate(newMembers, previousMembers) {
if (this.shouldPreserveState(newMembers, previousMembers)) {
this.updateMembersPartially(newMembers, previousMembers);
} else {
this.render();
}
}
```
## Best Practices
1. **Always Store UI State**: Use `setUIState()` for any user interaction
2. **Implement Partial Updates**: Override `updatePartial()` for efficient updates
3. **Use Change Detection**: Leverage `hasChanged()` to avoid unnecessary updates
4. **Batch Related Updates**: Use `batchUpdate()` for multiple property changes
5. **Test State Preservation**: Verify that UI state is maintained during data refreshes
## Troubleshooting
### Common Issues
1. **State Not Preserved**: Ensure you're using `setUIState()` and `getUIState()`
2. **Full Re-renders**: Check if `shouldPreserveState()` logic is correct
3. **Performance Issues**: Verify you're using partial updates instead of full renders
### Debug Tips
1. **Enable Console Logging**: Check browser console for state preservation logs
2. **Use State Indicators**: Monitor state changes in the test interface
3. **Verify Change Detection**: Ensure `hasChanged()` is working correctly
## Future Enhancements
- **State Synchronization**: Real-time state sync across multiple browser tabs
- **Advanced Change Detection**: Deep object comparison for complex data structures
- **State Persistence**: Save UI state to localStorage for session persistence
- **State Rollback**: Ability to revert to previous UI states