Android build v1.0.44

This commit is contained in:
Warren H
2026-06-20 23:04:39 -04:00
parent 6e10b37f8e
commit 4b45483ac3
10 changed files with 520 additions and 99 deletions
+144 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import type { Dispatch, SetStateAction } from 'react'
import type { CSSProperties, Dispatch, SetStateAction } from 'react'
type AdminItem = {
id: number
@@ -76,6 +76,49 @@ type AdminUpgradePath = {
toItemId: number
}
type AdminAbility = {
id: number
classId: number
slug: string
name: string
spellType: string
cost: number
cooldown: number
power: number
unlockLevel: number
glyph: string
description: string
}
type AdminTalent = {
id: number
classId: number
slug: string
name: string
maxRank: number
tier: number
branch: number
prerequisiteTalentId: number | null
prerequisiteRank: number
prerequisiteName: string | null
effectType: string
effectValuePerRank: number
glyph: string
description: string
}
type AdminClass = {
id: number
slug: string
name: string
resourceName: string
maxResource: number
themeColor: string
description: string
abilities: AdminAbility[]
talents: AdminTalent[]
}
type AdminData = {
items: AdminItem[]
encounters: AdminEncounter[]
@@ -84,9 +127,10 @@ type AdminData = {
craftingRecipes: AdminRecipe[]
dungeons: AdminDungeon[]
gearUpgradePaths: AdminUpgradePath[]
classes: AdminClass[]
}
type AdminTab = 'items' | 'dungeons' | 'encounters' | 'loot' | 'crafting' | 'upgrades'
type AdminTab = 'items' | 'dungeons' | 'encounters' | 'loot' | 'crafting' | 'upgrades' | 'classes'
type SavingState = Record<string, boolean>
type SetData = Dispatch<SetStateAction<AdminData | null>>
type SetSaving = Dispatch<SetStateAction<SavingState>>
@@ -99,6 +143,7 @@ const tabs: { id: AdminTab; label: string }[] = [
{ id: 'loot', label: 'Loot' },
{ id: 'crafting', label: 'Crafting' },
{ id: 'upgrades', label: 'Upgrades' },
{ id: 'classes', label: 'Classes' },
]
async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
@@ -143,6 +188,7 @@ export function AdminScreen({ onBack }: { onBack: () => void }) {
{tab === 'loot' && <LootTab data={data} setData={setData} setSaving={setSaving} saving={saving} />}
{tab === 'crafting' && <CraftingTab data={data} setData={setData} setSaving={setSaving} saving={saving} />}
{tab === 'upgrades' && <UpgradesTab data={data} setData={setData} setSaving={setSaving} saving={saving} />}
{tab === 'classes' && <ClassesTab data={data} />}
</section>
)
}
@@ -830,7 +876,9 @@ function CraftingTab({ data, setData, setSaving, saving }: {
)}
<h3 className="admin-loot-title">Required Components</h3>
{(!recipe || recipe.components.length === 0) && <p className="admin-empty">No component requirements.</p>}
{(!recipe || recipe.components.length === 0) && (
<p className="admin-empty">No component requirements. Crafting and upgrades are blocked until materials are added.</p>
)}
<div className="admin-loot-list">
{recipe?.components.map((comp) => (
<div key={comp.itemId} className="admin-loot-row">
@@ -981,7 +1029,7 @@ function UpgradesTab({ data, setData, setSaving, saving }: {
{target
? `Target requirements: ${targetRecipe && targetRecipe.components.length > 0
? targetRecipe.components.map((component) => `${component.quantity}x ${itemName(data, component.itemId)}`).join(', ')
: 'none'}`
: 'none configured - upgrade blocked until materials are added'}`
: 'No next upgrade selected.'}
</p>
<div className="admin-edit-actions">
@@ -1015,6 +1063,98 @@ function UpgradesTab({ data, setData, setSaving, saving }: {
)
}
function ClassesTab({ data }: { data: AdminData }) {
const [classId, setClassId] = useState(data.classes[0]?.id ?? 0)
const selectedClass = data.classes.find((candidate) => candidate.id === classId)
?? data.classes[0]
?? null
return (
<div className="admin-panel">
<div className="admin-class-layout">
<aside className="admin-class-list">
<p className="eyebrow">Classes</p>
{data.classes.map((gameClass) => (
<button
className={selectedClass?.id === gameClass.id ? 'active' : ''}
key={gameClass.id}
onClick={() => setClassId(gameClass.id)}
style={{ '--class-color': gameClass.themeColor } as CSSProperties}
type="button"
>
<span>{gameClass.name[0]}</span>
<div>
<strong>{gameClass.name}</strong>
<small>{gameClass.resourceName} {gameClass.maxResource}</small>
</div>
</button>
))}
</aside>
{selectedClass ? (
<section className="admin-class-detail">
<div className="admin-class-hero" style={{ '--class-color': selectedClass.themeColor } as CSSProperties}>
<span>{selectedClass.name[0]}</span>
<div>
<p className="eyebrow">{selectedClass.slug}</p>
<h2>{selectedClass.name}</h2>
<small>{selectedClass.resourceName} pool: {selectedClass.maxResource}</small>
</div>
<p>{selectedClass.description}</p>
</div>
<section>
<h3 className="admin-loot-title">Abilities ({selectedClass.abilities.length})</h3>
<div className="admin-class-table">
<div className="admin-class-table-head">
<span>Ability</span>
<span>Type</span>
<span>Default Strength</span>
<span>Cost</span>
<span>Cooldown</span>
<span>Unlock</span>
</div>
{selectedClass.abilities.map((ability) => (
<div key={ability.id} className="admin-class-row">
<span><i>{ability.glyph}</i><strong>{ability.name}</strong><small>{ability.description}</small></span>
<span>{ability.spellType}</span>
<span>{ability.power}</span>
<span>{ability.cost}</span>
<span>{ability.cooldown}s</span>
<span>Lvl {ability.unlockLevel}</span>
</div>
))}
</div>
</section>
<section>
<h3 className="admin-loot-title">Talents ({selectedClass.talents.length})</h3>
<div className="admin-class-talent-grid">
{selectedClass.talents.map((talent) => (
<article key={talent.id} className="admin-class-talent">
<div>
<span>{talent.glyph}</span>
<strong>{talent.name}</strong>
</div>
<small>Tier {talent.tier} · Branch {talent.branch} · Max {talent.maxRank}</small>
<p>{talent.description}</p>
<em>{talent.effectType}: {talent.effectValuePerRank}/rank</em>
{talent.prerequisiteName && (
<small>Requires {talent.prerequisiteName} rank {talent.prerequisiteRank}</small>
)}
</article>
))}
</div>
</section>
</section>
) : (
<p className="admin-empty">No classes found.</p>
)}
</div>
</div>
)
}
function jsonRequest(method: 'POST' | 'PUT', body: unknown): RequestInit {
return {
method,