mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { playersReplicant } from '../../../browser_shared/replicants';
|
|
import type { Schemas } from '../../../types';
|
|
|
|
type PlayersMap = Schemas.Players;
|
|
type Player = PlayersMap[string];
|
|
|
|
const STORAGE_KEY = 'scoreko-dev.players';
|
|
|
|
const normalizePlayer = (input: unknown): Player => {
|
|
const candidate = typeof input === 'object' && input !== null ? (input as Record<string, unknown>) : {};
|
|
return {
|
|
gamertag: typeof candidate.gamertag === 'string' ? candidate.gamertag : '',
|
|
team: typeof candidate.team === 'string' ? candidate.team : '',
|
|
country: typeof candidate.country === 'string' ? candidate.country : '',
|
|
twitter: typeof candidate.twitter === 'string' ? candidate.twitter : '',
|
|
realName: typeof candidate.realName === 'string' ? candidate.realName : '',
|
|
pronouns: typeof candidate.pronouns === 'string' ? candidate.pronouns : '',
|
|
twitch: typeof candidate.twitch === 'string' ? candidate.twitch : '',
|
|
notes: typeof candidate.notes === 'string' ? candidate.notes : '',
|
|
};
|
|
};
|
|
|
|
const normalizePlayers = (input: unknown): PlayersMap => {
|
|
if (typeof input !== 'object' || input === null) {
|
|
return {};
|
|
}
|
|
const result: PlayersMap = {};
|
|
Object.entries(input as Record<string, unknown>).forEach(([id, value]) => {
|
|
if (!id) {
|
|
return;
|
|
}
|
|
result[id] = normalizePlayer(value);
|
|
});
|
|
return result;
|
|
};
|
|
|
|
const readStorage = (): PlayersMap | null => {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
try {
|
|
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) {
|
|
return null;
|
|
}
|
|
const parsed = JSON.parse(raw) as unknown;
|
|
return normalizePlayers(parsed);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const writeStorage = (value: PlayersMap) => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
} catch {
|
|
// Ignore storage errors (quota, disabled, etc.)
|
|
}
|
|
};
|
|
|
|
export const usePlayersStore = defineStore('players', () => {
|
|
const players = ref<PlayersMap>({});
|
|
const replicant = playersReplicant;
|
|
const storageSnapshot = readStorage();
|
|
if (storageSnapshot) {
|
|
players.value = storageSnapshot;
|
|
}
|
|
|
|
const isApplyingReplicant = ref(false);
|
|
|
|
watch(
|
|
() => replicant?.data,
|
|
(value) => {
|
|
if (!value) {
|
|
return;
|
|
}
|
|
isApplyingReplicant.value = true;
|
|
players.value = normalizePlayers(value);
|
|
isApplyingReplicant.value = false;
|
|
writeStorage(players.value);
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
watch(
|
|
players,
|
|
(value) => {
|
|
writeStorage(value);
|
|
if (isApplyingReplicant.value || !replicant) {
|
|
return;
|
|
}
|
|
replicant.data = normalizePlayers(value);
|
|
replicant.save();
|
|
},
|
|
{ deep: true, flush: 'sync' }
|
|
);
|
|
|
|
const setPlayers = (value: PlayersMap) => {
|
|
players.value = normalizePlayers(value);
|
|
};
|
|
|
|
const upsertPlayer = (id: string, player: Player) => {
|
|
players.value = {
|
|
...players.value,
|
|
[id]: normalizePlayer(player),
|
|
};
|
|
};
|
|
|
|
const removePlayer = (id: string) => {
|
|
const next = { ...players.value };
|
|
delete next[id];
|
|
players.value = next;
|
|
};
|
|
|
|
const rows = computed(() => Object.entries(players.value).map(([id, player]) => ({
|
|
id,
|
|
...player,
|
|
})));
|
|
|
|
return {
|
|
players,
|
|
rows,
|
|
setPlayers,
|
|
upsertPlayer,
|
|
removePlayer,
|
|
};
|
|
});
|