feat: enhance character data loading with distinct statuses and genders, optimize epithets handling
All checks were successful
Build Docker Image / build (push) Successful in 1m42s
All checks were successful
Build Docker Image / build (push) Successful in 1m42s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { character, devilFruit, arc, characterOverride } from '$lib/server/db/schema';
|
import { character, devilFruit, arc, characterOverride } from '$lib/server/db/schema';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq, sql } from 'drizzle-orm';
|
||||||
import { fail } from '@sveltejs/kit';
|
import { fail } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { writeFile } from 'fs/promises';
|
import { writeFile } from 'fs/promises';
|
||||||
@@ -9,7 +9,7 @@ import { existsSync, mkdirSync } from 'fs';
|
|||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const [charactersData, devilFruits, arcs, overrides] = await Promise.all([
|
const [charactersData, devilFruits, arcs, overrides, statusesData, gendersData] = await Promise.all([
|
||||||
db
|
db
|
||||||
.select({
|
.select({
|
||||||
id: character.id,
|
id: character.id,
|
||||||
@@ -41,12 +41,36 @@ export const load: PageServerLoad = async () => {
|
|||||||
.orderBy(character.name),
|
.orderBy(character.name),
|
||||||
db.select().from(devilFruit).orderBy(devilFruit.name),
|
db.select().from(devilFruit).orderBy(devilFruit.name),
|
||||||
db.select().from(arc).orderBy(arc.name),
|
db.select().from(arc).orderBy(arc.name),
|
||||||
db.select().from(characterOverride)
|
db.select().from(characterOverride),
|
||||||
|
db.selectDistinct({ status: character.status })
|
||||||
|
.from(character)
|
||||||
|
.where(sql`${character.status} IS NOT NULL AND ${character.status} != ''`),
|
||||||
|
db.selectDistinct({ gender: character.gender })
|
||||||
|
.from(character)
|
||||||
|
.where(sql`${character.gender} IS NOT NULL AND ${character.gender} != ''`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create a map of overrides by characterId for easy lookup
|
// Create a map of overrides by characterId for easy lookup
|
||||||
const overridesMap = new Map(overrides.map((o) => [o.characterId, o]));
|
const overridesMap = new Map(overrides.map((o) => [o.characterId, o]));
|
||||||
|
|
||||||
|
// Create maps for arcs and devil fruits to lookup names by ID
|
||||||
|
const arcMap = new Map(arcs.map((a) => [a.id, a.name]));
|
||||||
|
const devilFruitMap = new Map(devilFruits.map((f) => [f.id, { name: f.name, type: f.type }]));
|
||||||
|
|
||||||
|
// Helper function to normalize data (parse JSON arrays)
|
||||||
|
const normalizeArray = (value: any): any => {
|
||||||
|
if (!value) return value;
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
if (typeof value === 'string' && value.includes('[')) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
// Merge character data with overrides
|
// Merge character data with overrides
|
||||||
const charactersWithOverrides = charactersData.map((char) => {
|
const charactersWithOverrides = charactersData.map((char) => {
|
||||||
const override = overridesMap.get(char.id);
|
const override = overridesMap.get(char.id);
|
||||||
@@ -59,8 +83,29 @@ export const load: PageServerLoad = async () => {
|
|||||||
displayValues[key as keyof typeof displayValues] = override[key as keyof typeof override];
|
displayValues[key as keyof typeof displayValues] = override[key as keyof typeof override];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update arcName if arcId was overridden
|
||||||
|
if (override.arcId !== null && override.arcId !== undefined) {
|
||||||
|
displayValues.arcName = arcMap.get(override.arcId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update devilFruitName and devilFruitType if devilFruitId was overridden
|
||||||
|
if (override.devilFruitId !== null && override.devilFruitId !== undefined) {
|
||||||
|
const fruit = devilFruitMap.get(override.devilFruitId);
|
||||||
|
displayValues.devilFruitName = fruit?.name || null;
|
||||||
|
displayValues.devilFruitType = fruit?.type || null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-normalize arrays (epithets, affiliations) for performance
|
||||||
|
displayValues.epithets = normalizeArray(displayValues.epithets);
|
||||||
|
displayValues.affiliations = normalizeArray(displayValues.affiliations);
|
||||||
|
|
||||||
|
// Create search text for epithets
|
||||||
|
displayValues.epithetsSearchText = Array.isArray(displayValues.epithets)
|
||||||
|
? displayValues.epithets.join(' ').toLowerCase()
|
||||||
|
: (displayValues.epithets || '').toLowerCase();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...char,
|
...char,
|
||||||
override,
|
override,
|
||||||
@@ -71,7 +116,15 @@ export const load: PageServerLoad = async () => {
|
|||||||
return {
|
return {
|
||||||
characters: charactersWithOverrides,
|
characters: charactersWithOverrides,
|
||||||
devilFruits,
|
devilFruits,
|
||||||
arcs
|
arcs,
|
||||||
|
availableStatuses: statusesData
|
||||||
|
.map(s => s.status)
|
||||||
|
.filter((s): s is string => !!s)
|
||||||
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
|
availableGenders: gendersData
|
||||||
|
.map(g => g.gender)
|
||||||
|
.filter((g): g is string => !!g)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,11 +190,16 @@ export const actions: Actions = {
|
|||||||
if (hasUploadedPicture && key === 'pictureUrl') {
|
if (hasUploadedPicture && key === 'pictureUrl') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Handle integers (age, bounty, height, devilFruitId, arcId)
|
// Handle integers (age, bounty, height)
|
||||||
if (key === 'age' || key === 'bounty' || key === 'height' || key === 'devilFruitId' || key === 'arcId') {
|
if (key === 'age' || key === 'bounty' || key === 'height') {
|
||||||
const strValue = value as string;
|
const strValue = value as string;
|
||||||
updates[key] = strValue && strValue !== '' ? parseInt(strValue) : null;
|
updates[key] = strValue && strValue !== '' ? parseInt(strValue) : null;
|
||||||
}
|
}
|
||||||
|
// Handle text IDs (devilFruitId, arcId)
|
||||||
|
else if (key === 'devilFruitId' || key === 'arcId') {
|
||||||
|
const strValue = value as string;
|
||||||
|
updates[key] = strValue && strValue !== '' ? strValue : null;
|
||||||
|
}
|
||||||
// Handle checkboxes (haki fields) after parsing all form data
|
// Handle checkboxes (haki fields) after parsing all form data
|
||||||
else if (key === 'hakiObservation' || key === 'hakiArmament' || key === 'hakiConqueror') {
|
else if (key === 'hakiObservation' || key === 'hakiArmament' || key === 'hakiConqueror') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -65,54 +65,14 @@
|
|||||||
status: ''
|
status: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableStatuses = $derived.by(() => {
|
|
||||||
const statuses = new Set<string>();
|
|
||||||
for (const char of data.characters) {
|
|
||||||
const status = char.displayValues.status;
|
|
||||||
if (status && String(status).trim() !== '') {
|
|
||||||
statuses.add(String(status));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(statuses).sort((a, b) => a.localeCompare(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
const availableGenders = $derived.by(() => {
|
|
||||||
const genders = new Set<string>();
|
|
||||||
for (const char of data.characters) {
|
|
||||||
const gender = char.displayValues.gender;
|
|
||||||
if (gender && String(gender).trim() !== '') {
|
|
||||||
genders.add(String(gender));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(genders).sort((a, b) => a.localeCompare(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredCharacters = $derived.by(() => {
|
const filteredCharacters = $derived.by(() => {
|
||||||
return data.characters.filter((char) => {
|
return data.characters.filter((char) => {
|
||||||
const normalizedQuery = searchQuery.toLowerCase().trim();
|
const normalizedQuery = searchQuery.toLowerCase().trim();
|
||||||
let epithetsText = '';
|
|
||||||
|
|
||||||
if (char.displayValues.epithets) {
|
|
||||||
if (typeof char.displayValues.epithets === 'string') {
|
|
||||||
if (char.displayValues.epithets.includes('[')) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(char.displayValues.epithets);
|
|
||||||
epithetsText = Array.isArray(parsed) ? parsed.join(' ') : String(parsed);
|
|
||||||
} catch {
|
|
||||||
epithetsText = char.displayValues.epithets;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
epithetsText = char.displayValues.epithets;
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(char.displayValues.epithets)) {
|
|
||||||
epithetsText = char.displayValues.epithets.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
normalizedQuery === '' ||
|
normalizedQuery === '' ||
|
||||||
char.displayValues.name.toLowerCase().includes(normalizedQuery) ||
|
char.displayValues.name.toLowerCase().includes(normalizedQuery) ||
|
||||||
epithetsText.toLowerCase().includes(normalizedQuery);
|
char.displayValues.epithetsSearchText.includes(normalizedQuery);
|
||||||
const matchesDaily =
|
const matchesDaily =
|
||||||
filterDaily === 'all' ||
|
filterDaily === 'all' ||
|
||||||
(filterDaily === 'daily' && char.displayValues.isInDailyMode) ||
|
(filterDaily === 'daily' && char.displayValues.isInDailyMode) ||
|
||||||
@@ -155,12 +115,12 @@
|
|||||||
epithets: override.epithets ?? '',
|
epithets: override.epithets ?? '',
|
||||||
pictureUrl: override.pictureUrl ?? '',
|
pictureUrl: override.pictureUrl ?? '',
|
||||||
url: override.url ?? '',
|
url: override.url ?? '',
|
||||||
devilFruitId: override.devilFruitId !== null && override.devilFruitId !== undefined ? override.devilFruitId : '',
|
devilFruitId: override.devilFruitId !== null && override.devilFruitId !== undefined ? override.devilFruitId : (char.devilFruitId || ''),
|
||||||
hakiObservation: override.hakiObservation ?? char.hakiObservation,
|
hakiObservation: override.hakiObservation ?? char.hakiObservation,
|
||||||
hakiArmament: override.hakiArmament ?? char.hakiArmament,
|
hakiArmament: override.hakiArmament ?? char.hakiArmament,
|
||||||
hakiConqueror: override.hakiConqueror ?? char.hakiConqueror,
|
hakiConqueror: override.hakiConqueror ?? char.hakiConqueror,
|
||||||
firstAppearance: override.firstAppearance ?? '',
|
firstAppearance: override.firstAppearance ?? '',
|
||||||
arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : '',
|
arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : (char.arcId || ''),
|
||||||
status: override.status ?? ''
|
status: override.status ?? ''
|
||||||
};
|
};
|
||||||
showOriginalValue = {};
|
showOriginalValue = {};
|
||||||
@@ -261,7 +221,7 @@
|
|||||||
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
<option value="all">All Statuses</option>
|
<option value="all">All Statuses</option>
|
||||||
{#each availableStatuses as status}
|
{#each data.availableStatuses as status}
|
||||||
<option value={status}>{status}</option>
|
<option value={status}>{status}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -270,7 +230,7 @@
|
|||||||
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
<option value="all">All Genders</option>
|
<option value="all">All Genders</option>
|
||||||
{#each availableGenders as gender}
|
{#each data.availableGenders as gender}
|
||||||
<option value={gender}>{gender}</option>
|
<option value={gender}>{gender}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -378,9 +338,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if char.displayValues.epithets}
|
{#if char.displayValues.epithets}
|
||||||
<span class="text-xs text-gray-500 truncate">
|
<span class="text-xs text-gray-500 truncate">
|
||||||
{typeof char.displayValues.epithets === 'string'
|
{Array.isArray(char.displayValues.epithets)
|
||||||
? (char.displayValues.epithets.includes('[') ? JSON.parse(char.displayValues.epithets).join(', ') : char.displayValues.epithets)
|
? char.displayValues.epithets.join(', ')
|
||||||
: char.displayValues.epithets.join(', ')}
|
: char.displayValues.epithets}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -393,13 +353,10 @@
|
|||||||
<!-- Affiliations -->
|
<!-- Affiliations -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'affiliations') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'affiliations') ? 'bg-amber-500/10' : ''}">
|
||||||
{#if char.displayValues.affiliations}
|
{#if char.displayValues.affiliations}
|
||||||
{@const parsedAffiliations = typeof char.displayValues.affiliations === 'string'
|
{#if Array.isArray(char.displayValues.affiliations) && char.displayValues.affiliations.length > 0}
|
||||||
? (char.displayValues.affiliations.includes('[') ? JSON.parse(char.displayValues.affiliations) : char.displayValues.affiliations.split(',').map((a: string) => a.trim()))
|
<span class="inline-block" title={char.displayValues.affiliations.join(', ')}>{char.displayValues.affiliations[0]}</span>
|
||||||
: char.displayValues.affiliations}
|
|
||||||
{#if Array.isArray(parsedAffiliations) && parsedAffiliations.length > 0}
|
|
||||||
<span class="inline-block" title={parsedAffiliations.join(', ')}>{parsedAffiliations[0]}</span>
|
|
||||||
{:else}
|
{:else}
|
||||||
{parsedAffiliations}
|
{char.displayValues.affiliations}
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
|
|||||||
Reference in New Issue
Block a user