- Updated character data structure to replace 'affiliations' and 'frAffiliations' with 'affiliation' and 'frAffiliation'. - Modified related functions and components to accommodate the new structure. - Adjusted database schema and server-side logic to reflect the changes in character affiliation handling. - Ensured all references in the UI components and data import/export scripts are updated accordingly.
499 lines
19 KiB
Svelte
499 lines
19 KiB
Svelte
<script lang="ts">
|
|
import { formatBounty } from '$lib';
|
|
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
|
import { language, t } from '$lib/i18n';
|
|
|
|
export let selectedCharacters: CharacterWithRelations[];
|
|
export let dailyCharacter: CharacterWithRelations;
|
|
export let columnVisibility: {
|
|
status?: boolean;
|
|
gender?: boolean;
|
|
affiliation?: boolean;
|
|
devilFruitType?: boolean;
|
|
haki?: boolean;
|
|
bounty?: boolean;
|
|
height?: boolean;
|
|
age?: boolean;
|
|
origin?: boolean;
|
|
arc?: boolean;
|
|
};
|
|
|
|
$: isFrench = $language === 'fr';
|
|
|
|
function getDisplayName(character: CharacterWithRelations): string {
|
|
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
|
|
return character.frName;
|
|
}
|
|
|
|
return character.name;
|
|
}
|
|
|
|
function getWikiUrl(character: CharacterWithRelations): string {
|
|
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
|
|
return character.frUrl;
|
|
}
|
|
|
|
return character.url || '';
|
|
}
|
|
|
|
function getWikiBaseUrl(): string {
|
|
return isFrench ? 'https://onepiece.fandom.com/fr/wiki/' : 'https://onepiece.fandom.com/wiki/';
|
|
}
|
|
|
|
function getDisplayOrigin(character: CharacterWithRelations): string | null {
|
|
if (isFrench && typeof character.frOrigin === 'string' && character.frOrigin.length > 0) {
|
|
return character.frOrigin;
|
|
}
|
|
|
|
return character.origin;
|
|
}
|
|
|
|
function hasMatchingOrigin(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean {
|
|
return getDisplayOrigin(characterEntry) === getDisplayOrigin(dailyEntry);
|
|
}
|
|
|
|
function getDisplayArcName(character: CharacterWithRelations): string | null {
|
|
if (isFrench && typeof character.frArcName === 'string' && character.frArcName.length > 0) {
|
|
return character.frArcName;
|
|
}
|
|
|
|
return character.arcName;
|
|
}
|
|
|
|
function getDislayAffiliation(character: CharacterWithRelations): string | null {
|
|
if (isFrench && typeof character.frAffiliation === 'string' && character.frAffiliation.length > 0) {
|
|
return character.frAffiliation;
|
|
}
|
|
|
|
return character.affiliation;
|
|
}
|
|
|
|
function hasMatchingArc(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean {
|
|
return getDisplayArcName(characterEntry) === getDisplayArcName(dailyEntry);
|
|
}
|
|
</script>
|
|
|
|
<section
|
|
class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
|
>
|
|
<div class="flex flex-col gap-4">
|
|
<div class="flex flex-col items-center gap-4 text-center">
|
|
<p class="text-xs font-semibold tracking-[0.28em] text-amber-100 uppercase">{$t.game.components.guessHistory.title}</p>
|
|
</div>
|
|
{#if selectedCharacters.length === 0}
|
|
<p class="text-center text-sm text-slate-200">{$t.game.components.guessHistory.empty}</p>
|
|
{:else}
|
|
<div class="-mx-6 overflow-x-auto px-6 pb-2 sm:mx-0 sm:px-0">
|
|
<div class="mx-auto w-max min-w-max">
|
|
<!-- Header -->
|
|
<div class="mb-2 flex gap-1 sm:gap-2">
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.character}
|
|
</p>
|
|
</div>
|
|
{#if columnVisibility.status !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.status}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.gender !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.gender}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.affiliation !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.affiliations}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.devilFruitType !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.fruit}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.haki !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.haki}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.bounty !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.bounty}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.height !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.height}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.age !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.age}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.origin !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.origin}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
{#if columnVisibility.arc !== false}
|
|
<div
|
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
|
>
|
|
<p
|
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
|
>
|
|
{$t.game.components.guessHistory.arc}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Rows -->
|
|
{#each selectedCharacters as character (character.id)}
|
|
<div class="mb-2 flex gap-1 sm:gap-2">
|
|
<!-- Personnage -->
|
|
<div
|
|
class="h-16 w-16 shrink-0 overflow-hidden rounded-lg border border-white/10 bg-slate-950/60 sm:h-20 sm:w-20 md:h-24 md:w-24"
|
|
>
|
|
{#if character.pictureUrl}
|
|
<a
|
|
href={getWikiBaseUrl() + getWikiUrl(character)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="block h-full w-full"
|
|
>
|
|
<img
|
|
src={character.pictureUrl}
|
|
alt={getDisplayName(character)}
|
|
class="h-full w-full cursor-pointer object-cover transition-opacity hover:opacity-80"
|
|
/>
|
|
</a>
|
|
{:else}
|
|
<div
|
|
class="flex h-full w-full items-center justify-center bg-slate-800 p-1 sm:p-2"
|
|
>
|
|
<span
|
|
class="line-clamp-3 text-center text-xs font-semibold sm:text-sm md:text-xl"
|
|
>{getDisplayName(character)}</span
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Vivant / Mort -->
|
|
{#if columnVisibility.status !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.status ===
|
|
dailyCharacter.status
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
|
{character.status === 'Alive'
|
|
? $t.game.components.guessHistory.alive
|
|
: character.status === 'Dead'
|
|
? $t.game.components.guessHistory.dead
|
|
: character.status === 'Unknown'
|
|
? $t.game.components.guessHistory.unknown
|
|
: character.status === null
|
|
? '-'
|
|
: character.status || $t.game.components.guessHistory.unknown}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Genre -->
|
|
{#if columnVisibility.gender !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.gender ===
|
|
dailyCharacter.gender
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
<p class="text-center text-xs font-bold text-white sm:text-sm md:text-base">
|
|
{character.gender === 'Male'
|
|
? $t.game.components.guessHistory.male
|
|
: character.gender === 'Female'
|
|
? $t.game.components.guessHistory.female
|
|
: character.gender || $t.game.components.guessHistory.unknown}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Affiliations -->
|
|
{#if columnVisibility.affiliation !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {getDislayAffiliation(character) === getDislayAffiliation(dailyCharacter)
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
|
{getDislayAffiliation(character) || $t.game.components.guessHistory.unknown}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Fruit -->
|
|
{#if columnVisibility.devilFruitType !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.devilFruitType ===
|
|
dailyCharacter.devilFruitType
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
{#if character.devilFruitType}
|
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
|
{character.devilFruitType}
|
|
</p>
|
|
{:else}
|
|
<p class="text-center text-2xl font-bold text-white sm:text-3xl md:text-5xl">
|
|
✕
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Haki -->
|
|
{#if columnVisibility.haki !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {(() => {
|
|
if (
|
|
character.hakiObservation === dailyCharacter.hakiObservation &&
|
|
character.hakiArmament === dailyCharacter.hakiArmament &&
|
|
character.hakiConqueror === dailyCharacter.hakiConqueror
|
|
) {
|
|
return 'bg-emerald-600/90';
|
|
} else if (
|
|
(character.hakiObservation && dailyCharacter.hakiObservation) ||
|
|
(character.hakiArmament && dailyCharacter.hakiArmament) ||
|
|
(character.hakiConqueror && dailyCharacter.hakiConqueror)
|
|
) {
|
|
return 'bg-yellow-600/80';
|
|
} else {
|
|
return 'bg-red-900/60';
|
|
}
|
|
})()} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
<p class="text-center text-sm font-bold text-white sm:text-lg md:text-2xl">
|
|
{#if character.hakiObservation}<span title={$t.game.components.guessHistory.obsHakiTitle}>👁️</span
|
|
>{/if}
|
|
{#if character.hakiArmament}<span title={$t.game.components.guessHistory.armHakiTitle}>🦾</span>{/if}
|
|
{#if character.hakiConqueror}<span title={$t.game.components.guessHistory.kingHakiTitle}>👑</span>{/if}
|
|
{#if !character.hakiObservation && !character.hakiArmament && !character.hakiConqueror}
|
|
<span class="text-2xl sm:text-3xl md:text-5xl">✕</span>
|
|
{/if}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Prime -->
|
|
{#if columnVisibility.bounty !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.bounty ===
|
|
dailyCharacter.bounty
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
|
>
|
|
{#if character.bounty != null && dailyCharacter.bounty != null && character.bounty !== dailyCharacter.bounty}
|
|
<div
|
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
|
style="
|
|
background-color: rgb(203, 213, 225);
|
|
clip-path: {character.bounty > dailyCharacter.bounty
|
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
|
"
|
|
></div>
|
|
{/if}
|
|
{#if character.bounty != null}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{formatBounty(character.bounty)} ฿
|
|
</p>
|
|
{:else}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{$t.game.components.guessHistory.unknown}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Taille -->
|
|
{#if columnVisibility.height !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.height ===
|
|
dailyCharacter.height
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
|
>
|
|
{#if character.height && dailyCharacter.height && character.height !== dailyCharacter.height}
|
|
<div
|
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
|
style="
|
|
background-color: rgb(203, 213, 225);
|
|
clip-path: {character.height > dailyCharacter.height
|
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
|
"
|
|
></div>
|
|
{/if}
|
|
{#if character.height}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{character.height} m
|
|
</p>
|
|
{:else}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{$t.game.components.guessHistory.unknown}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Age -->
|
|
{#if columnVisibility.age !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.age ===
|
|
dailyCharacter.age
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
|
>
|
|
{#if character.age != null && dailyCharacter.age != null && character.age !== dailyCharacter.age}
|
|
<div
|
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
|
style="
|
|
background-color: rgb(203, 213, 225);
|
|
clip-path: {character.age > dailyCharacter.age
|
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
|
"
|
|
></div>
|
|
{/if}
|
|
{#if character.age != null}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{character.age}
|
|
</p>
|
|
{:else}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{$t.game.components.guessHistory.unknown}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Origine -->
|
|
{#if columnVisibility.origin !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingOrigin(character, dailyCharacter)
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
|
>
|
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
|
{getDisplayOrigin(character) || $t.game.components.guessHistory.unknown}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Arc -->
|
|
{#if columnVisibility.arc !== false}
|
|
<div
|
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingArc(character, dailyCharacter)
|
|
? 'bg-emerald-600/90'
|
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
|
>
|
|
{#if !hasMatchingArc(character, dailyCharacter) && character.firstAppearance && dailyCharacter.firstAppearance && character.firstAppearance !== dailyCharacter.firstAppearance}
|
|
<div
|
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
|
style="
|
|
background-color: rgb(203, 213, 225);
|
|
clip-path: {character.firstAppearance > dailyCharacter.firstAppearance
|
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
|
"
|
|
></div>
|
|
{/if}
|
|
<p
|
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
|
>
|
|
{getDisplayArcName(character) || $t.game.components.guessHistory.unknown}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</section>
|