mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
refactor stores replicant and storage synchronization
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { commentaryReplicant } from '../../../browser_shared/replicants';
|
import { commentaryReplicant } from '../../../browser_shared/replicants';
|
||||||
import type { Schemas } from '../../../types';
|
import type { Schemas } from '../../../types';
|
||||||
|
import { syncStateWithReplicant } from './store-sync';
|
||||||
|
|
||||||
type Commentary = Schemas.Commentary;
|
type Commentary = Schemas.Commentary;
|
||||||
|
|
||||||
@@ -21,32 +22,7 @@ const normalizeCommentary = (input: unknown): Commentary => {
|
|||||||
export const useCommentaryStore = defineStore('commentary', () => {
|
export const useCommentaryStore = defineStore('commentary', () => {
|
||||||
const commentary = ref<Commentary>({ ...defaultCommentary });
|
const commentary = ref<Commentary>({ ...defaultCommentary });
|
||||||
const replicant = commentaryReplicant;
|
const replicant = commentaryReplicant;
|
||||||
const isApplyingReplicant = ref(false);
|
syncStateWithReplicant(commentary, replicant, normalizeCommentary);
|
||||||
|
|
||||||
watch(
|
|
||||||
() => replicant?.data,
|
|
||||||
(value) => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isApplyingReplicant.value = true;
|
|
||||||
commentary.value = normalizeCommentary(value);
|
|
||||||
isApplyingReplicant.value = false;
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
commentary,
|
|
||||||
(value) => {
|
|
||||||
if (isApplyingReplicant.value || !replicant) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
replicant.data = normalizeCommentary(value);
|
|
||||||
replicant.save();
|
|
||||||
},
|
|
||||||
{ deep: true, flush: 'sync' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const leftCommentator = computed({
|
const leftCommentator = computed({
|
||||||
get: () => commentary.value.leftCommentator,
|
get: () => commentary.value.leftCommentator,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { playersReplicant } from '../../../browser_shared/replicants';
|
import { playersReplicant } from '../../../browser_shared/replicants';
|
||||||
import type { Schemas } from '../../../types';
|
import type { Schemas } from '../../../types';
|
||||||
|
import { readStorageSnapshot, syncStateWithReplicant } from './store-sync';
|
||||||
|
|
||||||
type PlayersMap = Schemas.Players;
|
type PlayersMap = Schemas.Players;
|
||||||
type Player = PlayersMap[string];
|
type Player = PlayersMap[string];
|
||||||
@@ -33,69 +34,15 @@ const normalizePlayers = (input: unknown): PlayersMap => {
|
|||||||
return result;
|
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', () => {
|
export const usePlayersStore = defineStore('players', () => {
|
||||||
const players = ref<PlayersMap>({});
|
const players = ref<PlayersMap>({});
|
||||||
const replicant = playersReplicant;
|
const replicant = playersReplicant;
|
||||||
const storageSnapshot = readStorage();
|
const storageSnapshot = readStorageSnapshot(STORAGE_KEY, normalizePlayers);
|
||||||
if (storageSnapshot) {
|
if (storageSnapshot) {
|
||||||
players.value = storageSnapshot;
|
players.value = storageSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isApplyingReplicant = ref(false);
|
syncStateWithReplicant(players, replicant, normalizePlayers, STORAGE_KEY);
|
||||||
|
|
||||||
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) => {
|
const setPlayers = (value: PlayersMap) => {
|
||||||
players.value = normalizePlayers(value);
|
players.value = normalizePlayers(value);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { scoreboardReplicant } from '../../../browser_shared/replicants';
|
import { scoreboardReplicant } from '../../../browser_shared/replicants';
|
||||||
import type { Schemas } from '../../../types';
|
import type { Schemas } from '../../../types';
|
||||||
|
import { readStorageSnapshot, syncStateWithReplicant } from './store-sync';
|
||||||
|
|
||||||
type Scoreboard = Schemas.Scoreboard;
|
type Scoreboard = Schemas.Scoreboard;
|
||||||
|
|
||||||
@@ -44,69 +45,15 @@ const normalizeScoreboard = (input: unknown): Scoreboard => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const readStorage = (): Scoreboard | 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 normalizeScoreboard(parsed);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeStorage = (value: Scoreboard) => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
||||||
} catch {
|
|
||||||
// Ignore storage errors (quota, disabled, etc.)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useScoreboardStore = defineStore('scoreboard', () => {
|
export const useScoreboardStore = defineStore('scoreboard', () => {
|
||||||
const scoreboard = ref<Scoreboard>({ ...defaultScoreboard });
|
const scoreboard = ref<Scoreboard>({ ...defaultScoreboard });
|
||||||
const replicant = scoreboardReplicant;
|
const replicant = scoreboardReplicant;
|
||||||
const storageSnapshot = readStorage();
|
const storageSnapshot = readStorageSnapshot(STORAGE_KEY, normalizeScoreboard);
|
||||||
if (storageSnapshot) {
|
if (storageSnapshot) {
|
||||||
scoreboard.value = storageSnapshot;
|
scoreboard.value = storageSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isApplyingReplicant = ref(false);
|
syncStateWithReplicant(scoreboard, replicant, normalizeScoreboard, STORAGE_KEY);
|
||||||
|
|
||||||
watch(
|
|
||||||
() => replicant?.data,
|
|
||||||
(value) => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isApplyingReplicant.value = true;
|
|
||||||
scoreboard.value = normalizeScoreboard(value);
|
|
||||||
writeStorage(scoreboard.value);
|
|
||||||
isApplyingReplicant.value = false;
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
scoreboard,
|
|
||||||
(value) => {
|
|
||||||
writeStorage(value);
|
|
||||||
if (isApplyingReplicant.value || !replicant) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
replicant.data = normalizeScoreboard(value);
|
|
||||||
replicant.save();
|
|
||||||
},
|
|
||||||
{ deep: true, flush: 'sync' }
|
|
||||||
);
|
|
||||||
|
|
||||||
const setScoreboard = (value: Scoreboard) => {
|
const setScoreboard = (value: Scoreboard) => {
|
||||||
scoreboard.value = normalizeScoreboard(value);
|
scoreboard.value = normalizeScoreboard(value);
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { ref, watch, type Ref } from 'vue';
|
||||||
|
|
||||||
|
interface ReplicantLike<T> {
|
||||||
|
data: T | undefined;
|
||||||
|
save: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const readStorageSnapshot = <T>(
|
||||||
|
storageKey: string,
|
||||||
|
normalize: (input: unknown) => T,
|
||||||
|
): T | null => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(storageKey);
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalize(JSON.parse(raw) as unknown);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeStorageSnapshot = <T>(storageKey: string, value: T): void => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(storageKey, JSON.stringify(value));
|
||||||
|
} catch {
|
||||||
|
// Ignore storage errors (quota, disabled, etc.)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const syncStateWithReplicant = <T>(
|
||||||
|
state: Ref<T>,
|
||||||
|
replicant: ReplicantLike<T> | undefined,
|
||||||
|
normalize: (input: unknown) => T,
|
||||||
|
storageKey?: string,
|
||||||
|
): void => {
|
||||||
|
const isApplyingReplicant = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => replicant?.data,
|
||||||
|
(value) => {
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isApplyingReplicant.value = true;
|
||||||
|
state.value = normalize(value);
|
||||||
|
isApplyingReplicant.value = false;
|
||||||
|
|
||||||
|
if (storageKey) {
|
||||||
|
writeStorageSnapshot(storageKey, state.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
state,
|
||||||
|
(value) => {
|
||||||
|
if (storageKey) {
|
||||||
|
writeStorageSnapshot(storageKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isApplyingReplicant.value || !replicant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replicant.data = normalize(value);
|
||||||
|
replicant.save();
|
||||||
|
},
|
||||||
|
{ deep: true, flush: 'sync' },
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user