|
|
|
@@ -241,6 +241,7 @@ type ArtifactProfile = {
|
|
|
|
|
substats: string[];
|
|
|
|
|
targets: Array<{ stat: string; value: string }>;
|
|
|
|
|
targetConditions: Array<{
|
|
|
|
|
name?: string | null;
|
|
|
|
|
note: string | null;
|
|
|
|
|
rows: Array<{ stat: string; value: string }>;
|
|
|
|
|
}>;
|
|
|
|
@@ -255,6 +256,7 @@ type ConstellationValue = {
|
|
|
|
|
};
|
|
|
|
|
type ConstellationGroupValue = {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
note: string | null;
|
|
|
|
|
rows: ConstellationValue[];
|
|
|
|
|
};
|
|
|
|
@@ -286,6 +288,7 @@ type TeamValue = {
|
|
|
|
|
};
|
|
|
|
|
type TeamGroupValue = {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
note: string | null;
|
|
|
|
|
teams: TeamValue[];
|
|
|
|
|
};
|
|
|
|
@@ -315,6 +318,47 @@ function createClientId(prefix: string): string {
|
|
|
|
|
return `${prefix}-${Date.now()}-${clientIdCounter}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ConditionRenameDialog({
|
|
|
|
|
open,
|
|
|
|
|
fallback,
|
|
|
|
|
value,
|
|
|
|
|
onOpenChange,
|
|
|
|
|
onChange,
|
|
|
|
|
}: {
|
|
|
|
|
open: boolean;
|
|
|
|
|
fallback: string;
|
|
|
|
|
value: string;
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
onChange: (value: string) => void;
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Rename condition</DialogTitle>
|
|
|
|
|
<DialogDescription>
|
|
|
|
|
Leave blank to use {fallback}.
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<Field>
|
|
|
|
|
<FieldLabel htmlFor="condition-name">Condition name</FieldLabel>
|
|
|
|
|
<Input
|
|
|
|
|
id="condition-name"
|
|
|
|
|
autoFocus
|
|
|
|
|
maxLength={120}
|
|
|
|
|
placeholder={fallback}
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={(event) => onChange(event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<Button type="button" onClick={() => onOpenChange(false)}>Done</Button>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseEditorDraft<Draft>(
|
|
|
|
|
value: unknown,
|
|
|
|
|
arrayKeys: string[],
|
|
|
|
@@ -1122,6 +1166,7 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
const [activeGroupIndex, setActiveGroupIndex] = useState(0);
|
|
|
|
|
const [renamingWeaponGroupIndex, setRenamingWeaponGroupIndex] = useState<number | null>(null);
|
|
|
|
|
const rows = groups[activeGroupIndex]?.rows ?? [];
|
|
|
|
|
const setRows = (update: typeof rows | ((current: typeof rows) => typeof rows)) =>
|
|
|
|
|
setGroups((current) => current.map((group, index) => index === activeGroupIndex
|
|
|
|
@@ -1206,6 +1251,7 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
|
|
|
|
key={group.clientId}
|
|
|
|
|
value={String(index)}
|
|
|
|
|
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
|
|
|
|
onDoubleClick={() => setRenamingWeaponGroupIndex(index)}
|
|
|
|
|
>
|
|
|
|
|
{group.name.trim() || `Condition ${index + 1}`}
|
|
|
|
|
</ToggleGroupItem>
|
|
|
|
@@ -1242,18 +1288,13 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
|
|
|
|
<CopyIcon data-icon="inline-start" />Duplicate Condition
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<Field>
|
|
|
|
|
<FieldLabel htmlFor={`weapon-condition-name-${groups[activeGroupIndex]?.clientId}`}>
|
|
|
|
|
Condition name (optional)
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
<Input
|
|
|
|
|
id={`weapon-condition-name-${groups[activeGroupIndex]?.clientId}`}
|
|
|
|
|
maxLength={120}
|
|
|
|
|
placeholder={`Condition ${activeGroupIndex + 1}`}
|
|
|
|
|
value={groups[activeGroupIndex]?.name ?? ""}
|
|
|
|
|
onChange={(event) => setGroups(groups.map((group, index) => index === activeGroupIndex ? { ...group, name: event.target.value } : group))}
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
<ConditionRenameDialog
|
|
|
|
|
open={renamingWeaponGroupIndex !== null}
|
|
|
|
|
fallback={`Condition ${(renamingWeaponGroupIndex ?? 0) + 1}`}
|
|
|
|
|
value={renamingWeaponGroupIndex === null ? "" : groups[renamingWeaponGroupIndex]?.name ?? ""}
|
|
|
|
|
onOpenChange={(open) => { if (!open) setRenamingWeaponGroupIndex(null); }}
|
|
|
|
|
onChange={(name) => setGroups(groups.map((group, index) => index === renamingWeaponGroupIndex ? { ...group, name } : group))}
|
|
|
|
|
/>
|
|
|
|
|
<Field>
|
|
|
|
|
<FieldLabel htmlFor={`weapon-condition-note-${groups[activeGroupIndex]?.clientId}`}>
|
|
|
|
|
Condition note (optional)
|
|
|
|
@@ -1583,7 +1624,7 @@ function ArtifactEditor({
|
|
|
|
|
const [targetGroups, setTargetGroups] = useState(() => {
|
|
|
|
|
const source = profile?.targetConditions.length
|
|
|
|
|
? profile.targetConditions
|
|
|
|
|
: [{ note: null, rows: profile?.targets ?? [] }];
|
|
|
|
|
: [{ name: null, note: null, rows: profile?.targets ?? [] }];
|
|
|
|
|
return source.map((group, index) => {
|
|
|
|
|
const existing = new Map(group.rows.flatMap((target) => {
|
|
|
|
|
const stat = normalizeTargetStat(target.stat);
|
|
|
|
@@ -1591,12 +1632,14 @@ function ArtifactEditor({
|
|
|
|
|
}));
|
|
|
|
|
return {
|
|
|
|
|
clientId: `target-condition-${index}`,
|
|
|
|
|
name: group.name ?? "",
|
|
|
|
|
note: group.note ?? "",
|
|
|
|
|
rows: targetStatDefaults.map((target) => ({ stat: target.stat, value: existing.get(target.stat) ?? target.value })),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
const [activeTargetGroupIndex, setActiveTargetGroupIndex] = useState(0);
|
|
|
|
|
const [renamingTargetGroupIndex, setRenamingTargetGroupIndex] = useState<number | null>(null);
|
|
|
|
|
const targets = targetGroups[activeTargetGroupIndex]?.rows ?? [];
|
|
|
|
|
const setTargets = (rows: typeof targets) => setTargetGroups((current) => current.map((group, index) => index === activeTargetGroupIndex ? { ...group, rows } : group));
|
|
|
|
|
const [talents, setTalents] = useState<TalentPriority[]>(() => {
|
|
|
|
@@ -1635,6 +1678,7 @@ function ArtifactEditor({
|
|
|
|
|
circlet,
|
|
|
|
|
substats,
|
|
|
|
|
targetGroups: targetGroups.map((group) => ({
|
|
|
|
|
name: group.name.trim() || null,
|
|
|
|
|
note: group.note.trim() || null,
|
|
|
|
|
rows: group.rows.filter((target) => target.value.trim()).map((target) => ({ stat: target.stat, value: target.value.trim() })),
|
|
|
|
|
})),
|
|
|
|
@@ -1686,7 +1730,7 @@ function ArtifactEditor({
|
|
|
|
|
setGoblet(draft.goblet);
|
|
|
|
|
setCirclet(draft.circlet);
|
|
|
|
|
setSubstats(draft.substats);
|
|
|
|
|
setTargetGroups(draft.targetGroups);
|
|
|
|
|
setTargetGroups(draft.targetGroups.map((group) => ({ ...group, name: group.name ?? "" })));
|
|
|
|
|
setActiveTargetGroupIndex(draft.activeTargetGroupIndex ?? 0);
|
|
|
|
|
setTalents(draft.talents);
|
|
|
|
|
setNote(draft.note);
|
|
|
|
@@ -2054,19 +2098,26 @@ function ArtifactEditor({
|
|
|
|
|
if (values[0]) setActiveTargetGroupIndex(Number(values[0]));
|
|
|
|
|
}}>
|
|
|
|
|
{targetGroups.map((group, index) => (
|
|
|
|
|
<ToggleGroupItem key={group.clientId} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">
|
|
|
|
|
Condition {index + 1}
|
|
|
|
|
<ToggleGroupItem key={group.clientId} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground" onDoubleClick={() => setRenamingTargetGroupIndex(index)}>
|
|
|
|
|
{group.name.trim() || `Condition ${index + 1}`}
|
|
|
|
|
</ToggleGroupItem>
|
|
|
|
|
))}
|
|
|
|
|
</ToggleGroup>
|
|
|
|
|
<ConditionRenameDialog
|
|
|
|
|
open={renamingTargetGroupIndex !== null}
|
|
|
|
|
fallback={`Condition ${(renamingTargetGroupIndex ?? 0) + 1}`}
|
|
|
|
|
value={renamingTargetGroupIndex === null ? "" : targetGroups[renamingTargetGroupIndex]?.name ?? ""}
|
|
|
|
|
onOpenChange={(open) => { if (!open) setRenamingTargetGroupIndex(null); }}
|
|
|
|
|
onChange={(name) => setTargetGroups(targetGroups.map((group, index) => index === renamingTargetGroupIndex ? { ...group, name } : group))}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="button" variant="outline" disabled={targetGroups.length >= 20} onClick={() => {
|
|
|
|
|
setTargetGroups([...targetGroups, { clientId: createClientId("target-condition"), note: "", rows: targetStatDefaults.map((target) => ({ ...target })) }]);
|
|
|
|
|
setTargetGroups([...targetGroups, { clientId: createClientId("target-condition"), name: "", note: "", rows: targetStatDefaults.map((target) => ({ ...target })) }]);
|
|
|
|
|
setActiveTargetGroupIndex(targetGroups.length);
|
|
|
|
|
}}><PlusIcon data-icon="inline-start" />เพิ่ม Condition</Button>
|
|
|
|
|
<Button type="button" variant="outline" disabled={targetGroups.length >= 20} onClick={() => {
|
|
|
|
|
const source = targetGroups[activeTargetGroupIndex];
|
|
|
|
|
if (!source) return;
|
|
|
|
|
setTargetGroups([...targetGroups, { clientId: createClientId("target-condition"), note: source.note, rows: source.rows.map((row) => ({ ...row })) }]);
|
|
|
|
|
setTargetGroups([...targetGroups, { clientId: createClientId("target-condition"), name: source.name, note: source.note, rows: source.rows.map((row) => ({ ...row })) }]);
|
|
|
|
|
setActiveTargetGroupIndex(targetGroups.length);
|
|
|
|
|
}}><CopyIcon data-icon="inline-start" />Duplicate Condition</Button>
|
|
|
|
|
</div>
|
|
|
|
@@ -2278,17 +2329,20 @@ function ConstellationEditor({
|
|
|
|
|
const [groups, setGroups] = useState(() => {
|
|
|
|
|
const source = initial.constellationGroups.length
|
|
|
|
|
? initial.constellationGroups
|
|
|
|
|
: [{ id: "constellation-condition-default", note: null, rows: [] }];
|
|
|
|
|
: [{ id: "constellation-condition-default", name: null, note: null, rows: [] }];
|
|
|
|
|
return source.map((group) => ({
|
|
|
|
|
clientId: group.id,
|
|
|
|
|
name: group.name ?? "",
|
|
|
|
|
note: group.note ?? "",
|
|
|
|
|
rows: makeRows(group.rows),
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
const [activeConstellationGroupIndex, setActiveConstellationGroupIndex] = useState(0);
|
|
|
|
|
const [renamingConstellationGroupIndex, setRenamingConstellationGroupIndex] = useState<number | null>(null);
|
|
|
|
|
const value = {
|
|
|
|
|
note: note.trim() || null,
|
|
|
|
|
groups: groups.map((group) => ({
|
|
|
|
|
name: group.name.trim() || null,
|
|
|
|
|
note: group.note.trim() || null,
|
|
|
|
|
rows: group.rows
|
|
|
|
|
.filter((row) => row.description.trim() && row.cumulativePercent !== "")
|
|
|
|
@@ -2319,7 +2373,7 @@ function ConstellationEditor({
|
|
|
|
|
parseEditorDraft<typeof localDraft>(candidate, ["groups"], ["note"]),
|
|
|
|
|
restoreLocalDraft: (draft) => {
|
|
|
|
|
setNote(draft.note);
|
|
|
|
|
setGroups(draft.groups);
|
|
|
|
|
setGroups(draft.groups.map((group) => ({ ...group, name: group.name ?? "" })));
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return (
|
|
|
|
@@ -2355,11 +2409,19 @@ function ConstellationEditor({
|
|
|
|
|
key={group.clientId}
|
|
|
|
|
value={String(index)}
|
|
|
|
|
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
|
|
|
|
onDoubleClick={() => setRenamingConstellationGroupIndex(index)}
|
|
|
|
|
>
|
|
|
|
|
Condition {index + 1}
|
|
|
|
|
{group.name.trim() || `Condition ${index + 1}`}
|
|
|
|
|
</ToggleGroupItem>
|
|
|
|
|
))}
|
|
|
|
|
</ToggleGroup>
|
|
|
|
|
<ConditionRenameDialog
|
|
|
|
|
open={renamingConstellationGroupIndex !== null}
|
|
|
|
|
fallback={`Condition ${(renamingConstellationGroupIndex ?? 0) + 1}`}
|
|
|
|
|
value={renamingConstellationGroupIndex === null ? "" : groups[renamingConstellationGroupIndex]?.name ?? ""}
|
|
|
|
|
onOpenChange={(open) => { if (!open) setRenamingConstellationGroupIndex(null); }}
|
|
|
|
|
onChange={(name) => setGroups(groups.map((group, index) => index === renamingConstellationGroupIndex ? { ...group, name } : group))}
|
|
|
|
|
/>
|
|
|
|
|
{groups[activeConstellationGroupIndex] ? [groups[activeConstellationGroupIndex]].map((group) => {
|
|
|
|
|
const groupIndex = activeConstellationGroupIndex;
|
|
|
|
|
return (
|
|
|
|
@@ -2536,6 +2598,7 @@ function ConstellationEditor({
|
|
|
|
|
...groups,
|
|
|
|
|
{
|
|
|
|
|
clientId: createClientId("constellation-condition"),
|
|
|
|
|
name: "",
|
|
|
|
|
note: "",
|
|
|
|
|
rows: makeRows(),
|
|
|
|
|
},
|
|
|
|
@@ -2586,9 +2649,10 @@ function TeamEditor({
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
const [teamGroups, setTeamGroups] = useState(() => initial.teamGroups.length
|
|
|
|
|
? initial.teamGroups.map((group) => ({ clientId: group.id, note: group.note ?? "", teams: group.teams.map(({ id, ...team }) => ({ clientId: id, ...team })) }))
|
|
|
|
|
: [{ clientId: "team-condition-default", note: "", teams: [] as TeamToolValue[] }]);
|
|
|
|
|
? initial.teamGroups.map((group) => ({ clientId: group.id, name: group.name ?? "", note: group.note ?? "", teams: group.teams.map(({ id, ...team }) => ({ clientId: id, ...team })) }))
|
|
|
|
|
: [{ clientId: "team-condition-default", name: "", note: "", teams: [] as TeamToolValue[] }]);
|
|
|
|
|
const [activeTeamGroupIndex, setActiveTeamGroupIndex] = useState(0);
|
|
|
|
|
const [renamingTeamGroupIndex, setRenamingTeamGroupIndex] = useState<number | null>(null);
|
|
|
|
|
const teams = useMemo(() => teamGroups[activeTeamGroupIndex]?.teams ?? [], [activeTeamGroupIndex, teamGroups]);
|
|
|
|
|
const setTeams = (update: SetStateAction<TeamToolValue[]>) => setTeamGroups((current) => current.map((group, index) => index === activeTeamGroupIndex ? { ...group, teams: typeof update === "function" ? update(group.teams) : update } : group));
|
|
|
|
|
const [selectedTeamIds, setSelectedTeamIds] = useState<Set<string>>(
|
|
|
|
@@ -2636,7 +2700,7 @@ function TeamEditor({
|
|
|
|
|
}, searchQuery, searchTarget));
|
|
|
|
|
}, [artifacts, characters, searchQuery, searchTarget, teams, weapons]);
|
|
|
|
|
const value = {
|
|
|
|
|
groups: teamGroups.map((group) => ({ note: group.note.trim() || null, teams: group.teams.map((team) => ({
|
|
|
|
|
groups: teamGroups.map((group) => ({ name: group.name.trim() || null, note: group.note.trim() || null, teams: group.teams.map((team) => ({
|
|
|
|
|
title: team.title,
|
|
|
|
|
rotationSeconds: Number(team.rotationSeconds),
|
|
|
|
|
combo: team.combo,
|
|
|
|
@@ -2735,7 +2799,7 @@ function TeamEditor({
|
|
|
|
|
parseLocalDraft: (candidate) =>
|
|
|
|
|
parseEditorDraft<typeof localDraft>(candidate, ["teamGroups"]),
|
|
|
|
|
restoreLocalDraft: (draft) => {
|
|
|
|
|
setTeamGroups(draft.teamGroups);
|
|
|
|
|
setTeamGroups(draft.teamGroups.map((group) => ({ ...group, name: group.name ?? "" })));
|
|
|
|
|
setActiveTeamGroupIndex(draft.activeTeamGroupIndex ?? 0);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
@@ -2962,13 +3026,20 @@ function TeamEditor({
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{teamGroups.map((group, index) => (
|
|
|
|
|
<ToggleGroupItem key={group.clientId} value={String(index)}>
|
|
|
|
|
Condition {index + 1}
|
|
|
|
|
<ToggleGroupItem key={group.clientId} value={String(index)} onDoubleClick={() => setRenamingTeamGroupIndex(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">
|
|
|
|
|
{group.name.trim() || `Condition ${index + 1}`}
|
|
|
|
|
</ToggleGroupItem>
|
|
|
|
|
))}
|
|
|
|
|
</ToggleGroup>
|
|
|
|
|
<ConditionRenameDialog
|
|
|
|
|
open={renamingTeamGroupIndex !== null}
|
|
|
|
|
fallback={`Condition ${(renamingTeamGroupIndex ?? 0) + 1}`}
|
|
|
|
|
value={renamingTeamGroupIndex === null ? "" : teamGroups[renamingTeamGroupIndex]?.name ?? ""}
|
|
|
|
|
onOpenChange={(open) => { if (!open) setRenamingTeamGroupIndex(null); }}
|
|
|
|
|
onChange={(name) => setTeamGroups(teamGroups.map((group, index) => index === renamingTeamGroupIndex ? { ...group, name } : group))}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="button" variant="outline" disabled={teamGroups.length >= 20} onClick={() => {
|
|
|
|
|
setTeamGroups([...teamGroups, { clientId: createClientId("team-condition"), note: "", teams: [] }]);
|
|
|
|
|
setTeamGroups([...teamGroups, { clientId: createClientId("team-condition"), name: "", note: "", teams: [] }]);
|
|
|
|
|
setActiveTeamGroupIndex(teamGroups.length);
|
|
|
|
|
setSelectedTeamIds(new Set());
|
|
|
|
|
setSourceTeamId("");
|
|
|
|
@@ -2977,7 +3048,7 @@ function TeamEditor({
|
|
|
|
|
const source = teamGroups[activeTeamGroupIndex];
|
|
|
|
|
if (!source) return;
|
|
|
|
|
const copiedTeams = source.teams.map((team) => ({ ...team, clientId: createClientId("team"), members: team.members.map((member) => ({ ...member })) }));
|
|
|
|
|
setTeamGroups([...teamGroups, { clientId: createClientId("team-condition"), note: source.note, teams: copiedTeams }]);
|
|
|
|
|
setTeamGroups([...teamGroups, { clientId: createClientId("team-condition"), name: source.name, note: source.note, teams: copiedTeams }]);
|
|
|
|
|
setActiveTeamGroupIndex(teamGroups.length);
|
|
|
|
|
setSelectedTeamIds(new Set());
|
|
|
|
|
setSourceTeamId(copiedTeams[0]?.clientId ?? "");
|
|
|
|
|