feat: frontend optimization, refactoring

This commit is contained in:
2025-08-28 20:46:53 +02:00
parent 9486594199
commit c15654ef5a
4 changed files with 179 additions and 180 deletions

View File

@@ -1,5 +1,16 @@
// SPORE UI Framework - Component-based architecture with pub/sub system
// Lightweight logger with level gating
const logger = {
debug: (...args) => { try { if (window && window.DEBUG) { console.debug(...args); } } catch (_) { /* no-op */ } },
info: (...args) => console.info(...args),
warn: (...args) => console.warn(...args),
error: (...args) => console.error(...args),
};
if (typeof window !== 'undefined') {
window.logger = window.logger || logger;
}
// Event Bus for pub/sub communication
class EventBus {
constructor() {
@@ -99,24 +110,25 @@ class ViewModel {
// Set multiple properties at once with change detection
setMultiple(properties) {
const changedProperties = {};
const unchangedProperties = {};
// Determine changes and update previousData snapshot per key
Object.keys(properties).forEach(key => {
if (this._data[key] !== properties[key]) {
changedProperties[key] = properties[key];
} else {
unchangedProperties[key] = properties[key];
const newValue = properties[key];
const oldValue = this._data[key];
if (oldValue !== newValue) {
this._previousData[key] = oldValue;
changedProperties[key] = newValue;
}
});
// Set all properties
// Apply all properties
Object.keys(properties).forEach(key => {
this._data[key] = properties[key];
});
// Notify listeners only for changed properties
// Notify listeners only for changed properties with accurate previous values
Object.keys(changedProperties).forEach(key => {
this._notifyListeners(key, changedProperties[key], this._previousData[key]);
this._notifyListeners(key, this._data[key], this._previousData[key]);
});
if (Object.keys(changedProperties).length > 0) {
@@ -215,28 +227,33 @@ class ViewModel {
batchUpdate(updates, options = {}) {
const { preserveUIState = true, notifyChanges = true } = options;
if (preserveUIState) {
// Store current UI state
const currentUIState = new Map(this._uiState);
// Apply updates
Object.keys(updates).forEach(key => {
this._data[key] = updates[key];
});
// Restore UI state
// Optionally preserve UI state snapshot
const currentUIState = preserveUIState ? new Map(this._uiState) : null;
// Track which keys actually change and what the previous values were
const changedKeys = [];
Object.keys(updates).forEach(key => {
const newValue = updates[key];
const oldValue = this._data[key];
if (oldValue !== newValue) {
this._previousData[key] = oldValue;
this._data[key] = newValue;
changedKeys.push(key);
} else {
// Still apply to ensure consistency if needed
this._data[key] = newValue;
}
});
// Restore UI state if requested
if (preserveUIState && currentUIState) {
this._uiState = currentUIState;
} else {
// Apply updates normally
Object.keys(updates).forEach(key => {
this._data[key] = updates[key];
});
}
// Notify listeners if requested
// Notify listeners for changed keys
if (notifyChanges) {
Object.keys(updates).forEach(key => {
this._notifyListeners(key, updates[key], this._previousData[key]);
changedKeys.forEach(key => {
this._notifyListeners(key, this._data[key], this._previousData[key]);
});
}
}
@@ -521,6 +538,66 @@ class Component {
element.disabled = !enabled;
}
}
// Reusable render helpers
renderLoading(customHtml) {
const html = customHtml || `
<div class="loading">
<div>Loading...</div>
</div>
`;
this.setHTML('', html);
}
renderError(message) {
const safe = String(message || 'An error occurred');
const html = `
<div class="error">
<strong>Error:</strong><br>
${safe}
</div>
`;
this.setHTML('', html);
}
renderEmpty(customHtml) {
const html = customHtml || `
<div class="empty-state">
<div>No data</div>
</div>
`;
this.setHTML('', html);
}
// Tab helpers
setupTabs(container = this.container) {
const tabButtons = container.querySelectorAll('.tab-button');
const tabContents = container.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
this.addEventListener(button, 'click', (e) => {
e.stopPropagation();
const targetTab = button.dataset.tab;
this.setActiveTab(targetTab, container);
});
});
tabContents.forEach(content => {
this.addEventListener(content, 'click', (e) => {
e.stopPropagation();
});
});
}
setActiveTab(tabName, container = this.container) {
const tabButtons = container.querySelectorAll('.tab-button');
const tabContents = container.querySelectorAll('.tab-content');
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
const activeButton = container.querySelector(`[data-tab="${tabName}"]`);
const activeContent = container.querySelector(`#${tabName}-tab`);
if (activeButton) activeButton.classList.add('active');
if (activeContent) activeContent.classList.add('active');
logger.debug(`${this.constructor.name}: Active tab set to '${tabName}'`);
}
}
// Application class to manage components and routing