Replaces JSON.stringify snapshot comparison with a store.listen() dirty flag. Eliminates 5-15ms main-thread serialize on every poll tick when canvas is idle.
128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
// External imports
|
|
import { TLStore, TLEditorSnapshot, loadSnapshot, getSnapshot } from '@tldraw/tldraw';
|
|
// Local imports
|
|
import { logger } from '../../debugConfig';
|
|
import { LoadingState } from './snapshotService';
|
|
import { storageService, StorageKeys } from '../auth/localStorageService';
|
|
|
|
interface AutoSaveConfig {
|
|
checkInterval: number; // Changed to required
|
|
saveInterval: number; // Changed to required
|
|
}
|
|
|
|
const DEFAULT_CONFIG: AutoSaveConfig = {
|
|
checkInterval: 5000,
|
|
saveInterval: 30000
|
|
};
|
|
|
|
export class SharedStoreService {
|
|
private lastSaveTime: number = Date.now();
|
|
private autoSaveInterval: ReturnType<typeof setTimeout> | null = null;
|
|
private config: AutoSaveConfig;
|
|
private isDirty = false;
|
|
private dirtyListener: (() => void) | null = null;
|
|
|
|
constructor(private store: TLStore, config?: Partial<AutoSaveConfig>) {
|
|
this.config = {
|
|
...DEFAULT_CONFIG,
|
|
...config
|
|
};
|
|
this.dirtyListener = store.listen(() => {
|
|
this.isDirty = true;
|
|
});
|
|
logger.debug('shared-store-service', '🏗️ Initializing SharedStoreService');
|
|
}
|
|
|
|
public startAutoSave(setLoadingState: (state: LoadingState) => void): void {
|
|
if (this.autoSaveInterval) {
|
|
this.stopAutoSave();
|
|
}
|
|
|
|
this.autoSaveInterval = setInterval(() => {
|
|
this.checkAndSave(setLoadingState);
|
|
}, this.config.checkInterval);
|
|
|
|
logger.debug('shared-store-service', '⏰ Auto-save started', {
|
|
checkInterval: this.config.checkInterval,
|
|
saveInterval: this.config.saveInterval
|
|
});
|
|
}
|
|
|
|
public stopAutoSave(): void {
|
|
if (this.autoSaveInterval) {
|
|
clearInterval(this.autoSaveInterval);
|
|
this.autoSaveInterval = null;
|
|
logger.debug('shared-store-service', '⏹️ Auto-save stopped');
|
|
}
|
|
}
|
|
|
|
private async checkAndSave(setLoadingState: (state: LoadingState) => void): Promise<void> {
|
|
if (!this.isDirty) {
|
|
return;
|
|
}
|
|
|
|
this.isDirty = false;
|
|
const now = Date.now();
|
|
|
|
if (now - this.lastSaveTime >= this.config.saveInterval) {
|
|
await this.saveSnapshot(getSnapshot(this.store), setLoadingState);
|
|
this.lastSaveTime = now;
|
|
}
|
|
}
|
|
|
|
public async saveSnapshot(
|
|
snapshot: Partial<TLEditorSnapshot>,
|
|
setLoadingState: (state: LoadingState) => void
|
|
): Promise<void> {
|
|
try {
|
|
storageService.set(StorageKeys.LOCAL_SNAPSHOT, snapshot);
|
|
setLoadingState({ status: 'ready', error: '' });
|
|
logger.debug('shared-store-service', '✅ Snapshot saved successfully');
|
|
} catch (error) {
|
|
logger.error('shared-store-service', '❌ Failed to save snapshot:', error);
|
|
setLoadingState({
|
|
status: 'error',
|
|
error: error instanceof Error ? error.message : 'Failed to save snapshot'
|
|
});
|
|
}
|
|
}
|
|
|
|
public async loadSnapshot(
|
|
snapshot: Partial<TLEditorSnapshot>,
|
|
setLoadingState: (state: LoadingState) => void
|
|
): Promise<void> {
|
|
try {
|
|
setLoadingState({ status: 'loading', error: '' });
|
|
loadSnapshot(this.store, snapshot);
|
|
setLoadingState({ status: 'ready', error: '' });
|
|
logger.debug('shared-store-service', '✅ Snapshot loaded successfully');
|
|
} catch (error) {
|
|
logger.error('shared-store-service', '❌ Failed to load snapshot:', error);
|
|
this.store.clear();
|
|
setLoadingState({
|
|
status: 'error',
|
|
error: error instanceof Error ? error.message : 'Failed to load snapshot'
|
|
});
|
|
}
|
|
}
|
|
|
|
public getStore(): TLStore {
|
|
return this.store;
|
|
}
|
|
|
|
public clear(): void {
|
|
this.stopAutoSave();
|
|
this.dirtyListener?.();
|
|
this.dirtyListener = null;
|
|
this.store.clear();
|
|
this.isDirty = false;
|
|
logger.debug('shared-store-service', '🧹 Store cleared');
|
|
}
|
|
}
|
|
|
|
export const createSharedStore = (
|
|
store: TLStore,
|
|
config?: Partial<AutoSaveConfig>
|
|
): SharedStoreService => {
|
|
return new SharedStoreService(store, config);
|
|
};
|