187 lines
5.0 KiB
JavaScript
187 lines
5.0 KiB
JavaScript
/**
|
|
* State Manager - Manages application state
|
|
*/
|
|
|
|
export class StateManager {
|
|
constructor() {
|
|
this.state = {};
|
|
this.listeners = {};
|
|
|
|
// Load persisted state
|
|
this.loadPersistedState();
|
|
}
|
|
|
|
get(key) {
|
|
return this.state[key];
|
|
}
|
|
|
|
set(key, value) {
|
|
const oldValue = this.state[key];
|
|
this.state[key] = value;
|
|
|
|
// Persist certain keys
|
|
if (this.shouldPersist(key)) {
|
|
this.persistState(key, value);
|
|
}
|
|
|
|
// Notify listeners
|
|
this.notifyListeners(key, value, oldValue);
|
|
}
|
|
|
|
remove(key) {
|
|
const oldValue = this.state[key];
|
|
delete this.state[key];
|
|
|
|
// Remove from localStorage
|
|
if (this.shouldPersist(key)) {
|
|
localStorage.removeItem(`clean-tracks-${key}`);
|
|
}
|
|
|
|
// Notify listeners
|
|
this.notifyListeners(key, undefined, oldValue);
|
|
}
|
|
|
|
clear(keys = null) {
|
|
if (keys) {
|
|
keys.forEach(key => this.remove(key));
|
|
} else {
|
|
// Clear all state
|
|
Object.keys(this.state).forEach(key => this.remove(key));
|
|
}
|
|
}
|
|
|
|
// State persistence
|
|
shouldPersist(key) {
|
|
const persistedKeys = [
|
|
'userSettings',
|
|
'theme',
|
|
'language',
|
|
'defaultWordList',
|
|
'defaultCensorMethod',
|
|
'defaultWhisperModel',
|
|
// Onboarding-related state
|
|
'hasCompletedOnboarding',
|
|
'onboardingProgress',
|
|
'onboardingMilestones',
|
|
'onboardingVersion',
|
|
'onboardingSkipped',
|
|
'onboardingCompletedAt'
|
|
];
|
|
|
|
return persistedKeys.includes(key);
|
|
}
|
|
|
|
persistState(key, value) {
|
|
try {
|
|
localStorage.setItem(`clean-tracks-${key}`, JSON.stringify(value));
|
|
} catch (error) {
|
|
console.error('Error persisting state:', error);
|
|
}
|
|
}
|
|
|
|
loadPersistedState() {
|
|
const persistedKeys = [
|
|
'userSettings',
|
|
'theme',
|
|
'language',
|
|
'defaultWordList',
|
|
'defaultCensorMethod',
|
|
'defaultWhisperModel',
|
|
// Onboarding-related state
|
|
'hasCompletedOnboarding',
|
|
'onboardingProgress',
|
|
'onboardingMilestones',
|
|
'onboardingVersion',
|
|
'onboardingSkipped',
|
|
'onboardingCompletedAt'
|
|
];
|
|
|
|
persistedKeys.forEach(key => {
|
|
try {
|
|
const value = localStorage.getItem(`clean-tracks-${key}`);
|
|
if (value) {
|
|
this.state[key] = JSON.parse(value);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error loading persisted state for ${key}:`, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// State listeners
|
|
subscribe(key, listener) {
|
|
if (!this.listeners[key]) {
|
|
this.listeners[key] = [];
|
|
}
|
|
|
|
this.listeners[key].push(listener);
|
|
|
|
// Return unsubscribe function
|
|
return () => {
|
|
this.unsubscribe(key, listener);
|
|
};
|
|
}
|
|
|
|
unsubscribe(key, listener) {
|
|
if (this.listeners[key]) {
|
|
this.listeners[key] = this.listeners[key].filter(l => l !== listener);
|
|
}
|
|
}
|
|
|
|
notifyListeners(key, newValue, oldValue) {
|
|
if (this.listeners[key]) {
|
|
this.listeners[key].forEach(listener => {
|
|
try {
|
|
listener(newValue, oldValue, key);
|
|
} catch (error) {
|
|
console.error(`Error in state listener for ${key}:`, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Notify global listeners
|
|
if (this.listeners['*']) {
|
|
this.listeners['*'].forEach(listener => {
|
|
try {
|
|
listener(key, newValue, oldValue);
|
|
} catch (error) {
|
|
console.error('Error in global state listener:', error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Computed values
|
|
compute(key, computeFn) {
|
|
Object.defineProperty(this.state, key, {
|
|
get: computeFn,
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
// State snapshots
|
|
getSnapshot() {
|
|
return JSON.parse(JSON.stringify(this.state));
|
|
}
|
|
|
|
restoreSnapshot(snapshot) {
|
|
this.state = JSON.parse(JSON.stringify(snapshot));
|
|
|
|
// Notify all listeners
|
|
Object.keys(this.state).forEach(key => {
|
|
this.notifyListeners(key, this.state[key], undefined);
|
|
});
|
|
}
|
|
|
|
// Debugging
|
|
debug() {
|
|
console.group('State Manager Debug');
|
|
console.log('Current State:', this.state);
|
|
console.log('Listeners:', Object.keys(this.listeners).map(key => ({
|
|
key,
|
|
count: this.listeners[key].length
|
|
})));
|
|
console.groupEnd();
|
|
}
|
|
} |