Some checks failed
Test and deploy staging / staging (push) Failing after 2m50s
180 lines
5.4 KiB
Svelte
180 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import { deserialize } from '$app/forms';
|
|
import { Button } from '$lib/components/ui/button/index.js';
|
|
import Minus from 'lucide-svelte/icons/minus';
|
|
import Plus from 'lucide-svelte/icons/plus';
|
|
let {
|
|
itemId,
|
|
name,
|
|
level,
|
|
prestige,
|
|
levelCap = null,
|
|
prestigeCap = 1000,
|
|
prestigeRequiresMaxLevel = false,
|
|
compact = false,
|
|
dense = false,
|
|
disabled,
|
|
onupdate,
|
|
onedit
|
|
}: {
|
|
itemId: number;
|
|
name: string;
|
|
level: number;
|
|
prestige: number;
|
|
levelCap?: number | null;
|
|
prestigeCap?: number;
|
|
prestigeRequiresMaxLevel?: boolean;
|
|
compact?: boolean;
|
|
dense?: boolean;
|
|
disabled: boolean;
|
|
onupdate: (rank: { itemId: number; level: number; prestige: number }) => void;
|
|
onedit: () => void;
|
|
} = $props();
|
|
let saving = $state(false);
|
|
let error = $state('');
|
|
let saved = $state(false);
|
|
async function adjust(field: 'level' | 'prestige', delta: number) {
|
|
if (saving) return;
|
|
saving = true;
|
|
error = '';
|
|
saved = false;
|
|
const body = new FormData();
|
|
body.set('itemId', String(itemId));
|
|
body.set('field', field);
|
|
body.set('delta', String(delta));
|
|
try {
|
|
const result = deserialize(
|
|
await (await fetch('?/adjustWeaponRank', { method: 'POST', body })).text()
|
|
);
|
|
if (result.type !== 'success')
|
|
throw new Error(
|
|
result.type === 'failure'
|
|
? String(result.data?.message ?? 'Could not save.')
|
|
: 'Could not save.'
|
|
);
|
|
const rank = result.data?.rank as { itemId: number; level: number; prestige: number };
|
|
onupdate(rank);
|
|
saved = true;
|
|
} catch (e) {
|
|
error = e instanceof Error ? e.message : 'Could not save. Try again.';
|
|
} finally {
|
|
saving = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if dense}
|
|
<div class="relative flex shrink-0 items-center gap-0.5">
|
|
<button
|
|
type="button"
|
|
class="rounded px-1 py-1 text-[11px] tabular-nums hover:bg-accent"
|
|
aria-label={`Edit ${name} level and prestige`}
|
|
disabled={disabled || saving}
|
|
onclick={onedit}
|
|
title="Edit level and prestige"
|
|
>
|
|
<span class="sr-only">LEVEL</span>{level}{#if levelCap}<span class="text-muted-foreground"
|
|
>/{levelCap}</span
|
|
>{/if}
|
|
</button>
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
class="size-6"
|
|
aria-label={`Level up ${name}`}
|
|
disabled={disabled || saving || level >= (levelCap ?? 10000)}
|
|
onclick={() => adjust('level', 1)}><Plus class="size-3" /></Button
|
|
>
|
|
<span
|
|
role="status"
|
|
class={error
|
|
? 'absolute right-0 top-full z-20 w-52 rounded border bg-destructive p-2 text-xs text-white'
|
|
: 'sr-only'}>{saving ? 'Saving…' : error || (saved ? 'Saved' : '')}</span
|
|
>
|
|
</div>
|
|
{:else}
|
|
<div class="space-y-1">
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<div
|
|
class="flex items-center gap-1 rounded-lg border bg-background p-1"
|
|
aria-label={`${name} level controls`}
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
class={compact ? 'size-7' : 'size-10'}
|
|
aria-label={`Level down ${name}`}
|
|
disabled={disabled || saving || level <= 1}
|
|
onclick={() => adjust('level', -1)}
|
|
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
class={compact
|
|
? 'h-8 min-w-11 flex-col gap-0 px-1 tabular-nums'
|
|
: 'h-10 min-w-16 flex-col gap-0 px-2 tabular-nums'}
|
|
aria-label={`Edit ${name} level and prestige`}
|
|
disabled={disabled || saving}
|
|
onclick={onedit}
|
|
><span
|
|
class={compact
|
|
? 'text-[8px] text-muted-foreground'
|
|
: 'text-[10px] text-muted-foreground'}>LEVEL</span
|
|
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}
|
|
>{level}{#if levelCap}<span class="text-muted-foreground">/{levelCap}</span>{/if}</span
|
|
></Button
|
|
>
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
class={compact ? 'size-7' : 'size-10'}
|
|
aria-label={`Level up ${name}`}
|
|
disabled={disabled || saving || level >= (levelCap ?? 10000)}
|
|
onclick={() => adjust('level', 1)}
|
|
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
|
|
>
|
|
</div>
|
|
<div
|
|
class="flex items-center gap-1 rounded-lg border bg-background p-1"
|
|
aria-label={`${name} prestige controls`}
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
class={compact ? 'size-7' : 'size-10'}
|
|
aria-label={`Prestige down ${name}`}
|
|
disabled={disabled || saving || prestige <= 0}
|
|
onclick={() => adjust('prestige', -1)}
|
|
><Minus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
|
|
>
|
|
<span class="min-w-12 text-center tabular-nums"
|
|
><span
|
|
class={compact
|
|
? 'block text-[8px] text-muted-foreground'
|
|
: 'block text-[10px] text-muted-foreground'}>PRESTIGE</span
|
|
><span class={compact ? 'text-sm font-semibold' : 'text-lg font-semibold'}
|
|
>{prestige}{#if prestigeCap < 1000}<span class="text-muted-foreground"
|
|
>/{prestigeCap}</span
|
|
>{/if}</span
|
|
></span
|
|
>
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
class={compact ? 'size-7' : 'size-10'}
|
|
aria-label={`Prestige up ${name}`}
|
|
disabled={disabled ||
|
|
saving ||
|
|
prestige >= prestigeCap ||
|
|
(prestigeRequiresMaxLevel && level < (levelCap ?? 10000))}
|
|
onclick={() => adjust('prestige', 1)}
|
|
><Plus class={compact ? 'size-3' : 'size-4'} aria-hidden="true" /></Button
|
|
>
|
|
</div>
|
|
</div>
|
|
<p class="min-h-3 text-[9px] text-muted-foreground" role="status">
|
|
{saving ? 'Saving…' : error || (saved ? 'Saved' : 'Tap the level to edit both values')}
|
|
</p>
|
|
</div>
|
|
{/if}
|