fix
@@ -3,11 +3,12 @@
|
||||
"cid": "bafyreihgv33l3kdbpghum3ul5uugscfoaooyyx7nu3eaiuge4l6pru6je4",
|
||||
"value": {
|
||||
"item": [
|
||||
{ "id": 0, "name": "i", "text": { "en": "world", "ja": "世界" } },
|
||||
{ "id": 1, "name": "gordbox", "text": { "en": "The first color in this world", "ja": "この世界の最初の色" } },
|
||||
{ "id": 2, "name": "silverbox", "text": { "en": "The second color in this world", "ja": "この世界の2番目の色" } },
|
||||
{ "id": 3, "name": "bluebox", "text": { "en": "The third color in this world. It was once red, but was rewritten by someone", "ja": "この世界の3番目の色。かつては赤だったが、何者かに書き換えられた" } },
|
||||
{ "id": 4, "name": "copperbox", "text": { "en": "The Third Lost Color of the World", "ja": "この世界の失われた3番目の色" } }
|
||||
{ "id": 0, "name": "a", "text": { "en": "world", "ja": "世界" } },
|
||||
{ "id": 1, "name": "i", "text": { "en": "The Pillar of Creation at the center of the galaxy stands tall in a place called the Eternal Stone, which is said to tolerate no change. The three attributes of gold, silver, and copper are engraved on it.", "ja": "銀河の中心にある創造の柱。そこにそびえ立つ永遠の石には金、銀、銅というこの世界の3つの属性が刻み込まれている" } },
|
||||
{ "id": 2, "name": "gordbox", "text": { "en": "The first color in this world", "ja": "この世界の最初の色" } },
|
||||
{ "id": 3, "name": "silverbox", "text": { "en": "The second color in this world", "ja": "この世界の2番目の色" } },
|
||||
{ "id": 4, "name": "bluebox", "text": { "en": "The third color in this world. It was once red, but was rewritten by someone", "ja": "この世界の3番目の色。かつては赤だったが、何者かに書き換えられた" } },
|
||||
{ "id": 5, "name": "copperbox", "text": { "en": "The Third Lost Color of the World", "ja": "この世界から失われた3番目の色" } }
|
||||
],
|
||||
"$type": "ai.syui.rse.admin",
|
||||
"system": [
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
BIN
public/rse/item/5.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -156,13 +156,9 @@ export function renderCardPage(
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by unique first, then rarity (desc), then by id
|
||||
// Sort by id
|
||||
const sortedGroups = Array.from(cardGroups.values())
|
||||
.sort((a, b) => {
|
||||
if (a.card.unique !== b.card.unique) return a.card.unique ? -1 : 1
|
||||
if (b.card.rare !== a.card.rare) return b.card.rare - a.card.rare
|
||||
return a.card.id - b.card.id
|
||||
})
|
||||
.sort((a, b) => a.card.id - b.card.id)
|
||||
|
||||
const cardsHtml = sortedGroups.map(({ card, count }) => {
|
||||
return renderCard(card, '/card', count)
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
// 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
|
||||
cp: number
|
||||
@@ -15,6 +25,18 @@ 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'
|
||||
@@ -99,13 +121,27 @@ function renderCharacterSection(
|
||||
}
|
||||
|
||||
// Render single item
|
||||
function renderRseItem(item: RseItem): string {
|
||||
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">
|
||||
@@ -117,6 +153,7 @@ function renderRseItem(item: RseItem): string {
|
||||
<div class="card-detail">
|
||||
<span class="card-cp">${item.cp}</span>
|
||||
</div>
|
||||
${infoHtml}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -126,7 +163,8 @@ export function renderRsePage(
|
||||
collection: RseCollection | null,
|
||||
handle: string,
|
||||
userCards: UserCard[] = [],
|
||||
adminData: CardAdminData | null = null
|
||||
adminData: CardAdminData | null = null,
|
||||
rseAdminData: RseAdminData | null = null
|
||||
): string {
|
||||
const jsonUrl = `/@${handle}/at/collection/ai.syui.rse.user/self`
|
||||
|
||||
@@ -157,7 +195,7 @@ export function renderRsePage(
|
||||
const charsHtml = sortedChars.map(c =>
|
||||
renderCharacterSection(c, userCards, adminData)
|
||||
).join('')
|
||||
const itemsHtml = sortedItems.map(i => renderRseItem(i)).join('')
|
||||
const itemsHtml = sortedItems.map(i => renderRseItem(i, rseAdminData)).join('')
|
||||
|
||||
return `
|
||||
<div class="card-page">
|
||||
|
||||
@@ -728,6 +728,56 @@ export async function getCardAdmin(did: string): Promise<CardAdminData | null> {
|
||||
return null
|
||||
}
|
||||
|
||||
// RSE admin data types
|
||||
export interface RseAdminItem {
|
||||
id: number
|
||||
name: string
|
||||
text: { ja: string; en: string }
|
||||
}
|
||||
|
||||
export interface RseAdminData {
|
||||
item: RseAdminItem[]
|
||||
ability: unknown[]
|
||||
character: unknown[]
|
||||
system: unknown[]
|
||||
collection: unknown[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// Get RSE admin data (ai.syui.rse.admin)
|
||||
export async function getRseAdmin(did: string): Promise<RseAdminData | null> {
|
||||
const collection = 'ai.syui.rse.admin'
|
||||
|
||||
// Try local first
|
||||
try {
|
||||
const res = await fetch(`/content/${did}/${collection}/self.json`)
|
||||
if (res.ok && isJsonResponse(res)) {
|
||||
const record = await res.json()
|
||||
return record.value as RseAdminData
|
||||
}
|
||||
} catch {
|
||||
// Try remote
|
||||
}
|
||||
|
||||
// Remote fallback
|
||||
const pds = await getPds(did)
|
||||
if (!pds) return null
|
||||
|
||||
try {
|
||||
const host = pds.replace('https://', '')
|
||||
const url = `${xrpcUrl(host, comAtprotoRepo.getRecord)}?repo=${did}&collection=${collection}&rkey=self`
|
||||
const res = await fetch(url)
|
||||
if (res.ok) {
|
||||
const record = await res.json()
|
||||
return record.value as RseAdminData
|
||||
}
|
||||
} catch {
|
||||
// Failed
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Get user's links (ai.syui.at.link)
|
||||
export async function getLinks(did: string): Promise<LinkCollection | null> {
|
||||
const collection = 'ai.syui.at.link'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './styles/main.css'
|
||||
import './styles/card.css'
|
||||
import './styles/card-migrate.css'
|
||||
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getLinks } from './lib/api'
|
||||
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getRseAdmin, getLinks } from './lib/api'
|
||||
import { parseRoute, onRouteChange, navigate, type Route } from './lib/router'
|
||||
import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks } from './lib/auth'
|
||||
import { validateRecord } from './lib/lexicon'
|
||||
@@ -256,13 +256,14 @@ async function render(route: Route): Promise<void> {
|
||||
// RSE page with character cards
|
||||
const cardCollection = config.cardCollection || 'ai.syui.card.user'
|
||||
const adminDid = config.bot?.did || config.did || did
|
||||
const [rseData, cards, adminData] = await Promise.all([
|
||||
const [rseData, cards, adminData, rseAdminData] = await Promise.all([
|
||||
getRse(did),
|
||||
getCards(did, cardCollection),
|
||||
getCardAdmin(adminDid)
|
||||
getCardAdmin(adminDid),
|
||||
getRseAdmin(adminDid)
|
||||
])
|
||||
const userCards = cards?.card || []
|
||||
html += `<div id="content">${renderRsePage(rseData, handle, userCards, adminData)}</div>`
|
||||
html += `<div id="content">${renderRsePage(rseData, handle, userCards, adminData, rseAdminData)}</div>`
|
||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||
|
||||
} else if (route.type === 'link') {
|
||||
|
||||