Files
buzz-sheet/lib/guides/team-tools.ts
T
2026-09-03 23:24:51 +07:00

116 lines
3.4 KiB
TypeScript

export interface TeamToolMember {
characterKey: string;
weaponKey: string | null;
artifactKey: string | null;
secondArtifactKey: string | null;
constellation: number;
refinement: number;
damage: string;
}
export interface TeamToolValue {
clientId: string;
title: string;
rotationSeconds: string;
combo: string;
members: TeamToolMember[];
}
export const teamApplyFields = ["loadout", "damage", "rotation", "combo"] as const;
export type TeamApplyField = (typeof teamApplyFields)[number];
export interface TeamPreset {
id: string;
name: string;
createdAt: string;
team: Omit<TeamToolValue, "clientId">;
}
function cloneMember(member: TeamToolMember): TeamToolMember {
return { ...member };
}
function cloneTeam(team: TeamToolValue): TeamToolValue {
return { ...team, members: team.members.map(cloneMember) };
}
export function applyTeamFields(
teams: readonly TeamToolValue[],
sourceId: string,
targetIds: ReadonlySet<string>,
fields: readonly TeamApplyField[],
): TeamToolValue[] {
const source = teams.find((team) => team.clientId === sourceId);
if (!source) return teams.map(cloneTeam);
const selected = new Set(fields);
return teams.map((team) => {
if (!targetIds.has(team.clientId) || team.clientId === sourceId) return cloneTeam(team);
const next = cloneTeam(team);
if (selected.has("loadout")) {
next.members = source.members.map((member, index) => ({
...cloneMember(member),
damage: next.members[index]?.damage ?? "0",
}));
}
if (selected.has("damage")) next.members = source.members.map(cloneMember);
if (selected.has("rotation")) next.rotationSeconds = source.rotationSeconds;
if (selected.has("combo")) next.combo = source.combo;
return next;
});
}
export function applyPresetFields(
teams: readonly TeamToolValue[],
preset: TeamPreset,
targetIds: ReadonlySet<string>,
fields: readonly TeamApplyField[],
): TeamToolValue[] {
return applyTeamFields(
[
...teams,
{ clientId: preset.id, ...preset.team, members: preset.team.members.map(cloneMember) },
],
preset.id,
targetIds,
fields,
).slice(0, teams.length);
}
export function presetFingerprint(team: Omit<TeamToolValue, "clientId">): string {
return JSON.stringify(team);
}
export function limitPresets(presets: readonly TeamPreset[], limit = 5): TeamPreset[] {
const seen = new Set<string>();
return presets.filter((preset) => {
const fingerprint = presetFingerprint(preset.team);
if (seen.has(fingerprint)) return false;
seen.add(fingerprint);
return true;
}).slice(0, limit);
}
export function parseTeamPresets(serialized: string | null): TeamPreset[] {
if (!serialized) return [];
try {
const value: unknown = JSON.parse(serialized);
if (!Array.isArray(value)) return [];
return limitPresets(value.filter((item): item is TeamPreset => {
if (!item || typeof item !== "object") return false;
const candidate = item as Record<string, unknown>;
return typeof candidate.id === "string" && typeof candidate.name === "string" &&
typeof candidate.createdAt === "string" && Boolean(candidate.team);
}));
} catch {
return [];
}
}
export function teamPresetStorageKey(guideId: string): string {
return `buzz-sheet:team-presets:${guideId}`;
}
export function teamPresetCookieKey(guideId: string): string {
return `buzz-sheet-team-presets-${guideId}`;
}