mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
b32c0e4560
- 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.
219 lines
6.3 KiB
TypeScript
219 lines
6.3 KiB
TypeScript
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,
|
|
};
|
|
});
|