Merge pull request #60 from jhbruhn/refactor/remove-cross-store-dependencies
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions

refactor: Remove cross-store dependencies using Zustand event store
This commit is contained in:
Jan-Henrik Bruhn 2025-12-27 17:46:53 +01:00 committed by GitHub
commit a173ee33a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 103 additions and 11 deletions

52
src/stores/storeEvents.ts Normal file
View file

@ -0,0 +1,52 @@
/**
* Store Events
*
* Zustand-based event store for cross-store communication without tight coupling.
* Uses Zustand's built-in subscription system to emit and react to events.
*/
import { create } from "zustand";
interface EventState {
// Event counters - incrementing these triggers subscriptions
patternDeletedCount: number;
// Actions to emit events
emitPatternDeleted: () => void;
}
/**
* Event store using Zustand for cross-store communication.
* Stores can emit events by calling actions, and subscribe to events using Zustand's subscribe.
*/
export const useEventStore = create<EventState>((set) => ({
patternDeletedCount: 0,
emitPatternDeleted: () => {
set((state) => ({ patternDeletedCount: state.patternDeletedCount + 1 }));
},
}));
/**
* Subscribe to the pattern deleted event.
*
* The subscription remains active until the returned unsubscribe function is called.
* If the unsubscribe function is not called, the listener will persist for the
* lifetime of the event store (typically the lifetime of the application).
*
* Call the returned unsubscribe function when the listener is no longer needed,
* especially for short-lived components or non-module-level subscriptions.
*
* @param callback - Function to call when the event is emitted.
* @returns Unsubscribe function that removes the listener when invoked.
*/
export const onPatternDeleted = (callback: () => void): (() => void) => {
let prevCount = useEventStore.getState().patternDeletedCount;
return useEventStore.subscribe((state) => {
if (state.patternDeletedCount !== prevCount) {
prevCount = state.patternDeletedCount;
callback();
}
});
};

View file

@ -1,6 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { onPatternDeleted } from "./storeEvents";
/** /**
* Machine Cache Store * Machine Cache Store
@ -192,3 +193,18 @@ export const useMachineCacheStore = create<MachineCacheState>((set, get) => ({
}); });
}, },
})); }));
// Subscribe to pattern deleted event.
// This subscription is intended to persist for the lifetime of the application,
// so the unsubscribe function returned by `onPatternDeleted` is intentionally
// not stored or called.
onPatternDeleted(() => {
try {
useMachineCacheStore.getState().clearResumeState();
} catch (error) {
console.error(
"[MachineCacheStore] Failed to clear resume state on pattern deleted event:",
error,
);
}
});

View file

@ -13,7 +13,7 @@ import { SewingMachineError } from "../utils/errorCodeHelpers";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { createStorageService } from "../platform"; import { createStorageService } from "../platform";
import type { IStorageService } from "../platform/interfaces/IStorageService"; import type { IStorageService } from "../platform/interfaces/IStorageService";
import { usePatternStore } from "./usePatternStore"; import { useEventStore } from "./storeEvents";
interface MachineState { interface MachineState {
// Service instances // Service instances
@ -291,16 +291,8 @@ export const useMachineStore = create<MachineState>((set, get) => ({
sewingProgress: null, sewingProgress: null,
}); });
// Clear uploaded pattern data in pattern store // Emit pattern deleted event for other stores to react
usePatternStore.getState().clearUploadedPattern(); useEventStore.getState().emitPatternDeleted();
// Clear upload state in upload store
const { useMachineUploadStore } = await import("./useMachineUploadStore");
useMachineUploadStore.getState().reset();
// Clear resume state in cache store
const { useMachineCacheStore } = await import("./useMachineCacheStore");
useMachineCacheStore.getState().clearResumeState();
await refreshStatus(); await refreshStatus();
} catch (err) { } catch (err) {

View file

@ -1,6 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { uuidToString } from "../services/PatternCacheService"; import { uuidToString } from "../services/PatternCacheService";
import { onPatternDeleted } from "./storeEvents";
/** /**
* Machine Upload Store * Machine Upload Store
@ -126,3 +127,18 @@ export const useMachineUploadStore = create<MachineUploadState>((set) => ({
set({ uploadProgress: 0, isUploading: false }); set({ uploadProgress: 0, isUploading: false });
}, },
})); }));
// Subscribe to pattern deleted event.
// This subscription is intended to persist for the lifetime of the application,
// so the unsubscribe function returned by `onPatternDeleted` is intentionally
// not stored or called.
onPatternDeleted(() => {
try {
useMachineUploadStore.getState().reset();
} catch (error) {
console.error(
"[MachineUploadStore] Failed to reset on pattern deleted event:",
error,
);
}
});

View file

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import type { PesPatternData } from "../formats/import/pesImporter"; import type { PesPatternData } from "../formats/import/pesImporter";
import { onPatternDeleted } from "./storeEvents";
interface PatternState { interface PatternState {
// Original pattern (pre-upload) // Original pattern (pre-upload)
@ -121,3 +122,18 @@ export const useUploadedPatternOffset = () =>
usePatternStore((state) => state.uploadedPatternOffset); usePatternStore((state) => state.uploadedPatternOffset);
export const usePatternRotation = () => export const usePatternRotation = () =>
usePatternStore((state) => state.patternRotation); usePatternStore((state) => state.patternRotation);
// Subscribe to pattern deleted event.
// This subscription is intended to persist for the lifetime of the application,
// so the unsubscribe function returned by `onPatternDeleted` is intentionally
// not stored or called.
onPatternDeleted(() => {
try {
usePatternStore.getState().clearUploadedPattern();
} catch (error) {
console.error(
"[PatternStore] Failed to clear uploaded pattern on pattern deleted event:",
error,
);
}
});