add card.admin, rse.admin

This commit is contained in:
2026-01-28 16:04:08 +09:00
parent cfa36633e1
commit 9682b8f039
23 changed files with 933 additions and 42 deletions

View File

@@ -1,4 +1,15 @@
// RSE display component for ai.syui.rse.user collection
import { renderCard, type UserCard, type CardAdminEntry, type CardAdminData } from './card'
export interface RseAdminItem {
id: number
name: string
text: { ja: string; en: string }
}
export interface RseAdminData {
item: RseAdminItem[]
}
export interface RseItem {
id: number
@@ -14,31 +25,135 @@ export interface RseCollection {
updatedAt: string
}
// Get current language
function getLang(): string {
return localStorage.getItem('preferredLang') || 'ja'
}
// Get localized text
function getLocalizedText(obj: { ja: string; en: string } | undefined): string {
if (!obj) return ''
const lang = getLang()
return obj[lang as 'ja' | 'en'] || obj.ja || obj.en || ''
}
// Get rarity class from unique flag
function getRarityClass(item: RseItem): string {
if (item.unique) return 'unique'
return ''
}
// Render single item/character
function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
// Get cards for a character (character 0 = cards 0-99, character 1 = cards 100-199, etc.)
function getCardsForCharacter(
characterId: number,
userCards: UserCard[],
adminData: CardAdminData | null
): { card: UserCard; adminEntry?: CardAdminEntry }[] {
const minId = characterId * 100
const maxId = minId + 99
// Build admin lookup map
const adminMap = new Map<number, CardAdminEntry>()
if (adminData?.card) {
for (const entry of adminData.card) {
adminMap.set(entry.id, entry)
}
}
// Filter and dedupe cards for this character
const cardGroups = new Map<number, UserCard>()
for (const card of userCards) {
if (card.id >= minId && card.id <= maxId) {
const existing = cardGroups.get(card.id)
if (!existing || card.cp > existing.cp || card.unique) {
cardGroups.set(card.id, card)
}
}
}
// Sort by ID and add admin entries
return Array.from(cardGroups.values())
.sort((a, b) => a.id - b.id)
.map(card => ({
card,
adminEntry: adminMap.get(card.id)
}))
}
// Render character section with its cards below
function renderCharacterSection(
item: RseItem,
userCards: UserCard[],
adminData: CardAdminData | null
): string {
const rarityClass = getRarityClass(item)
const effectsHtml = rarityClass ? `
<div class="card-status pattern-${rarityClass}"></div>
<div class="card-status color-${rarityClass}"></div>
` : ''
// Get cards for this character
const characterCards = getCardsForCharacter(item.id, userCards, adminData)
const cardsHtml = characterCards.map(({ card, adminEntry }) =>
renderCard(card, '/card', undefined, adminEntry)
).join('')
return `
<div class="rse-character-section">
<div class="rse-character-main">
<div class="card-item">
<div class="card-wrapper">
<div class="card-reflection">
<img src="/rse/character/${item.id}.webp" alt="character ${item.id}" loading="lazy" />
</div>
${effectsHtml}
</div>
<div class="card-detail">
<span class="card-cp">${item.cp}</span>
</div>
</div>
</div>
${characterCards.length > 0 ? `
<div class="rse-card-grid">${cardsHtml}</div>
` : ''}
</div>
`
}
// Render single item
function renderRseItem(item: RseItem, rseAdminData: RseAdminData | null): string {
const rarityClass = getRarityClass(item)
const effectsHtml = rarityClass ? `
<div class="card-status pattern-${rarityClass}"></div>
<div class="card-status color-${rarityClass}"></div>
` : ''
// Get admin entry for this item
const adminEntry = rseAdminData?.item?.find(i => i.id === item.id)
const name = adminEntry?.name || ''
const text = adminEntry ? getLocalizedText(adminEntry.text) : ''
const infoHtml = (name || text) ? `
<div class="card-info">
<div class="card-info-header">
<span class="card-info-name">${name}</span>
</div>
${text ? `<div class="card-info-text">${text}</div>` : ''}
</div>
` : ''
return `
<div class="card-item">
<div class="card-wrapper">
<div class="card-reflection">
<img src="/rse/${type}/${item.id}.webp" alt="${type} ${item.id}" loading="lazy" />
<img src="/rse/item/${item.id}.webp" alt="item ${item.id}" loading="lazy" />
</div>
${effectsHtml}
</div>
<div class="card-detail">
<span class="card-cp">${item.cp}</span>
</div>
${infoHtml}
</div>
`
}
@@ -46,7 +161,10 @@ function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
// Render RSE page
export function renderRsePage(
collection: RseCollection | null,
handle: string
handle: string,
userCards: UserCard[] = [],
adminData: CardAdminData | null = null,
rseAdminData: RseAdminData | null = null
): string {
const jsonUrl = `/@${handle}/at/collection/ai.syui.rse.user/self`
@@ -70,19 +188,14 @@ export function renderRsePage(
const totalItems = items.length
const uniqueChars = characters.filter(c => c.unique).length
// Sort by unique > id
const sortedChars = [...characters].sort((a, b) => {
if (a.unique !== b.unique) return a.unique ? -1 : 1
return a.id - b.id
})
// Sort by id
const sortedChars = [...characters].sort((a, b) => a.id - b.id)
const sortedItems = [...items].sort((a, b) => a.id - b.id)
const sortedItems = [...items].sort((a, b) => {
if (a.unique !== b.unique) return a.unique ? -1 : 1
return a.id - b.id
})
const charsHtml = sortedChars.map(c => renderRseItem(c, 'character')).join('')
const itemsHtml = sortedItems.map(i => renderRseItem(i, 'item')).join('')
const charsHtml = sortedChars.map(c =>
renderCharacterSection(c, userCards, adminData)
).join('')
const itemsHtml = sortedItems.map(i => renderRseItem(i, rseAdminData)).join('')
return `
<div class="card-page">
@@ -107,7 +220,7 @@ export function renderRsePage(
</div>
${charsHtml ? `
<h3 class="rse-section-title">Characters</h3>
<div class="card-grid">${charsHtml}</div>
<div class="rse-characters">${charsHtml}</div>
` : ''}
${itemsHtml ? `
<h3 class="rse-section-title">Items</h3>