mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
feat: implement replicant state synchronization for commentary, players, scoreboard, and graphics settings
- Added a new service for synchronizing state with replicants in `replicant-state-service.ts`. - Refactored commentary store to utilize the new synchronization service. - Created a new graphics settings store that syncs with replicants. - Introduced a packs store for managing installed packs and their states. - Updated players and scoreboard stores to use the new synchronization service. - Created shared services for managing replicated state in graphics components. - Refactored existing components to use the new shared services for replicant state. - Added normalization and default values for commentary, graphics settings, players, and scoreboard. - Improved type safety and organization in shared domain files for better maintainability.
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { createPackService } from '../services/pack-service';
|
||||
import {
|
||||
buildCharactersByGame,
|
||||
buildDefaultCharactersByGame,
|
||||
type DefaultCharacterPair,
|
||||
type FightingCharacterOption,
|
||||
} from '../../shared/domain/packs/characters';
|
||||
import type {
|
||||
GameSelectOption,
|
||||
PackDownloadState,
|
||||
PackManifest,
|
||||
PackRegistry,
|
||||
PackUpdateInfo,
|
||||
} from '../../shared/domain/packs/types';
|
||||
|
||||
const packService = createPackService();
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
}
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
const getLocalLogoUrl = (packId: string): string => `/packs/${packId}/logo.png`;
|
||||
|
||||
export const usePacksStore = defineStore('packs', () => {
|
||||
const initialized = ref(false);
|
||||
const registry = ref<PackRegistry | null>(null);
|
||||
const installedPackIds = ref<string[]>([]);
|
||||
const downloadStates = ref<Record<string, PackDownloadState>>({});
|
||||
const availableUpdates = ref<Record<string, PackUpdateInfo>>({});
|
||||
const installedManifests = ref<Record<string, PackManifest>>({});
|
||||
const loadingManifestIds = new Set<string>();
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
let registryRefreshTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const installedManifestList = computed(() =>
|
||||
installedPackIds.value
|
||||
.map((packId) => installedManifests.value[packId])
|
||||
.filter((manifest): manifest is PackManifest => Boolean(manifest)),
|
||||
);
|
||||
|
||||
const charactersByGame = computed(() => buildCharactersByGame(installedManifestList.value));
|
||||
const defaultCharactersByGame = computed(() => buildDefaultCharactersByGame(installedManifestList.value));
|
||||
|
||||
const allGameOptions = computed<GameSelectOption[]>(() => {
|
||||
if (!registry.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return registry.value.packs.map((entry) => ({
|
||||
label: entry.name,
|
||||
value: entry.name,
|
||||
available: installedPackIds.value.includes(entry.id),
|
||||
registryEntry: entry,
|
||||
updateInfo: availableUpdates.value[entry.id],
|
||||
}));
|
||||
});
|
||||
|
||||
const updateCount = computed(() => Object.keys(availableUpdates.value).length);
|
||||
|
||||
const loadInstalledManifest = (packId: string): void => {
|
||||
if (installedManifests.value[packId] || loadingManifestIds.has(packId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadingManifestIds.add(packId);
|
||||
packService.readLocalManifest(packId)
|
||||
.then((manifest) => {
|
||||
installedManifests.value = {
|
||||
...installedManifests.value,
|
||||
[packId]: manifest,
|
||||
};
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error(`[packs] Failed to load manifest for "${packId}":`, error);
|
||||
})
|
||||
.finally(() => {
|
||||
loadingManifestIds.delete(packId);
|
||||
});
|
||||
};
|
||||
|
||||
const syncInstalledManifests = (nextPackIds: string[]): void => {
|
||||
const nextSet = new Set(nextPackIds);
|
||||
const nextManifests: Record<string, PackManifest> = {};
|
||||
|
||||
Object.entries(installedManifests.value).forEach(([packId, manifest]) => {
|
||||
if (nextSet.has(packId)) {
|
||||
nextManifests[packId] = manifest;
|
||||
}
|
||||
});
|
||||
installedManifests.value = nextManifests;
|
||||
|
||||
nextPackIds.forEach(loadInstalledManifest);
|
||||
};
|
||||
|
||||
const initialize = (): void => {
|
||||
if (initialized.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialized.value = true;
|
||||
packService.subscribe({
|
||||
onRegistryChanged: (value) => {
|
||||
registry.value = value;
|
||||
},
|
||||
onInstalledPacksChanged: (value) => {
|
||||
installedPackIds.value = [...value];
|
||||
syncInstalledManifests(value);
|
||||
},
|
||||
onDownloadStatesChanged: (value) => {
|
||||
downloadStates.value = { ...value };
|
||||
},
|
||||
onAvailableUpdatesChanged: (value) => {
|
||||
availableUpdates.value = { ...value };
|
||||
},
|
||||
})
|
||||
.then((dispose) => {
|
||||
unsubscribe = dispose;
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
initialized.value = false;
|
||||
console.error('[packs] Failed to subscribe to pack replicants:', error);
|
||||
});
|
||||
};
|
||||
|
||||
const dispose = (): void => {
|
||||
unsubscribe?.();
|
||||
unsubscribe = null;
|
||||
if (registryRefreshTimer) {
|
||||
clearInterval(registryRefreshTimer);
|
||||
registryRefreshTimer = null;
|
||||
}
|
||||
initialized.value = false;
|
||||
};
|
||||
|
||||
const runCommand = (label: string, command: () => Promise<void>): void => {
|
||||
command().catch((error: unknown) => {
|
||||
console.error(`[packs] ${label} failed:`, error);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRegistry = (): void => {
|
||||
runCommand('fetchRegistry', packService.fetchRegistry);
|
||||
};
|
||||
|
||||
const startRegistryRefresh = (intervalMs = 15_000): void => {
|
||||
fetchRegistry();
|
||||
if (registryRefreshTimer) {
|
||||
return;
|
||||
}
|
||||
registryRefreshTimer = setInterval(fetchRegistry, intervalMs);
|
||||
};
|
||||
|
||||
const stopRegistryRefresh = (): void => {
|
||||
if (!registryRefreshTimer) {
|
||||
return;
|
||||
}
|
||||
clearInterval(registryRefreshTimer);
|
||||
registryRefreshTimer = null;
|
||||
};
|
||||
|
||||
const downloadPack = (packId: string): void => {
|
||||
runCommand(`downloadPack "${packId}"`, () => packService.downloadPack(packId));
|
||||
};
|
||||
|
||||
const uninstallPack = (packId: string): void => {
|
||||
runCommand(`uninstallPack "${packId}"`, () => packService.uninstallPack(packId));
|
||||
};
|
||||
|
||||
const updatePack = (packId: string): void => {
|
||||
runCommand(`updatePack "${packId}"`, () => packService.updatePack(packId));
|
||||
};
|
||||
|
||||
const isGameAvailable = (gameName: string): boolean => {
|
||||
const entry = registry.value?.packs.find((pack) => pack.name === gameName);
|
||||
return entry ? installedPackIds.value.includes(entry.id) : false;
|
||||
};
|
||||
|
||||
const getDownloadState = (packId: string): PackDownloadState =>
|
||||
downloadStates.value[packId] ?? { status: 'idle', progress: 0 };
|
||||
|
||||
const getCharactersByGame = (gameName: string): FightingCharacterOption[] =>
|
||||
charactersByGame.value[gameName] ?? [];
|
||||
|
||||
const getDefaultCharactersByGame = (gameName: string): DefaultCharacterPair | undefined =>
|
||||
defaultCharactersByGame.value[gameName];
|
||||
|
||||
return {
|
||||
registry,
|
||||
installedPackIds,
|
||||
downloadStates,
|
||||
availableUpdates,
|
||||
installedManifests,
|
||||
allGameOptions,
|
||||
updateCount,
|
||||
initialize,
|
||||
dispose,
|
||||
fetchRegistry,
|
||||
startRegistryRefresh,
|
||||
stopRegistryRefresh,
|
||||
downloadPack,
|
||||
uninstallPack,
|
||||
updatePack,
|
||||
isGameAvailable,
|
||||
getDownloadState,
|
||||
getCharactersByGame,
|
||||
getDefaultCharactersByGame,
|
||||
formatBytes,
|
||||
getLocalLogoUrl,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user