232 lines
21 KiB
Svelte
232 lines
21 KiB
Svelte
<script lang="ts">
|
||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||
import SearchableAssignment from '$lib/components/SearchableAssignment.svelte';
|
||
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
|
||
import SortableColumnHeader from '$lib/components/SortableColumnHeader.svelte';
|
||
import RegistryInlineEditor from '$lib/components/RegistryInlineEditor.svelte';
|
||
import KpiFilters from '$lib/components/KpiFilters.svelte';
|
||
import GameIcon from '$lib/components/GameIcon.svelte';
|
||
import type { GameIconName } from '$lib/game-icons';
|
||
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
|
||
import GameDataRenderUploadField from '$lib/components/GameDataRenderUploadField.svelte';
|
||
import { WEAPON_CATEGORIES } from '$lib/weapons/category-bonuses';
|
||
import {
|
||
excludedTierZeroWeaponModSlot,
|
||
expandSharedWeaponModSlots,
|
||
sharedWeaponModSlots,
|
||
weaponModSlotsComplete
|
||
} from '$lib/weapons/mod-slots';
|
||
let { data, form } = $props();
|
||
const categories = [...WEAPON_CATEGORIES];
|
||
const categoryOptions = categories.map((value) => ({ value, label: value, icon: value.toLowerCase() as GameIconName, iconOnly: true }));
|
||
const tierOptions = [0, 1, 2].map((value) => ({ value: String(value), label: `T${value}` }));
|
||
const damageTypes = ['Blast', 'Pierce', 'Shredding'];
|
||
const iconName = (value: string): GameIconName => value.toLowerCase() as GameIconName;
|
||
const damageTypeOptions = damageTypes.map((value) => ({ value, label: value, icon: iconName(value), iconOnly: true }));
|
||
const blankTier = (tier: number) => ({ tier, damage_min: 0, damage_max: 0 });
|
||
const blank = () => ({ id: '', name: '', category: 'AR', is_exotic: 0, native_tier: '0', damage_type: 'Pierce', rpm: 0, magazine: 0, reload_speed: 0, accuracy: 0, stability: 0, exotic_talent_id: '', active: 1, renderUrl: '', tiers: [blankTier(0), blankTier(1), blankTier(2)], modSlots: [0,1,2].map((tier) => ({ tier, slots: [] })), sharedModSlots: [], t0ExcludedSlot: '', talent1Ids: [], talent2Ids: [], augmentEffects: [{ stat_attribute_id: '', value: 0, sort_order: 1 }] });
|
||
let editing = $state<any | null>(null);
|
||
let bonusEditing = $state<any | null>(null);
|
||
let handledResultToken = $state('');
|
||
let renderResetToken = $state(0);
|
||
let openedRequestedWeapon = $state(false);
|
||
const edit = (weapon: any) => {
|
||
const tiers = [0, 1, 2].map((tier) => {
|
||
const saved = weapon.tiers.find((item: any) => Number(item.tier) === tier);
|
||
return { ...blankTier(tier), ...saved };
|
||
});
|
||
const modSlots = [0,1,2].map((tier) => ({ tier, slots: [...(weapon.modSlots?.find((row: any) => Number(row.tier) === tier)?.slots ?? [])] }));
|
||
const sharedSlots = sharedWeaponModSlots(modSlots);
|
||
editing = { ...weapon, native_tier: String(weapon.native_tier), tiers, modSlots, sharedModSlots: sharedSlots, t0ExcludedSlot: excludedTierZeroWeaponModSlot(sharedSlots, modSlots), talent1Ids: [...weapon.talent1Ids], talent2Ids: [...weapon.talent2Ids], augmentEffects: weapon.augmentEffects.map((effect: any) => ({ ...effect })) };
|
||
bonusEditing = null;
|
||
};
|
||
const editBonusProfile = (profile: any) => {
|
||
bonusEditing = { category: profile.category, effects: profile.effects.map((effect: any) => ({ ...effect })) };
|
||
editing = null;
|
||
};
|
||
const tierAt = (tier: number) => editing.tiers.find((item: any) => Number(item.tier) === tier);
|
||
const modSlotPayload = () => expandSharedWeaponModSlots(editing.sharedModSlots, editing.t0ExcludedSlot);
|
||
const t0SlotCount = () => editing.t0ExcludedSlot ? Math.max(0, editing.sharedModSlots.length - 1) : 0;
|
||
const weaponState = (weapon: any) => weapon.pending
|
||
? 'PENDING'
|
||
: Number(weapon.active) !== 1
|
||
? 'ARCHIVED'
|
||
: weaponModSlotsComplete(Number(weapon.native_tier), weapon.modSlots)
|
||
? 'ACTIVE'
|
||
: 'CONFIG REQUIRED';
|
||
const statLabel = (id: string) => data.stats.find((stat) => stat.id === id)?.name ?? 'Unknown attribute';
|
||
const talentLabel = (id: string) => [...data.talent1Options, ...data.talent2Options].find((talent) => talent.id === id)?.name ?? 'Unknown talent';
|
||
const setCategory = (category: string) => {
|
||
if (!editing) return;
|
||
editing.category = category;
|
||
};
|
||
const setExotic = (isExotic: boolean) => {
|
||
if (!editing) return;
|
||
editing.is_exotic = isExotic ? 1 : 0;
|
||
if (!isExotic) editing.exotic_talent_id = '';
|
||
};
|
||
const exoticTalentOptions = () => data.exoticTalentOptions.map((talent: any) => {
|
||
const assignedElsewhere = Boolean(talent.ownerWeaponId && talent.ownerWeaponId !== editing?.id);
|
||
return {
|
||
...talent,
|
||
detail: `${talent.ownerWeaponName
|
||
? `ASSIGNED TO ${String(talent.ownerWeaponName).toLocaleUpperCase()}`
|
||
: 'AVAILABLE FOR ASSIGNMENT'} · TIER II ${talent.tierIIDocumented ? 'DOCUMENTED' : 'NOT YET DOCUMENTED'}`,
|
||
disabled: assignedElsewhere,
|
||
disabledReason: assignedElsewhere
|
||
? `Already assigned to ${talent.ownerWeaponName}`
|
||
: ''
|
||
};
|
||
});
|
||
const addAugmentEffect = () => {
|
||
if (editing && editing.augmentEffects.length < 2) editing.augmentEffects = [...editing.augmentEffects, { stat_attribute_id: '', value: 0, sort_order: 2 }];
|
||
};
|
||
const removeAugmentEffect = (index: number) => {
|
||
if (editing && index > 0) editing.augmentEffects = editing.augmentEffects.filter((_: any, effectIndex: number) => effectIndex !== index);
|
||
};
|
||
$effect(() => {
|
||
if (editing?.t0ExcludedSlot && !editing.sharedModSlots.includes(editing.t0ExcludedSlot)) {
|
||
editing.t0ExcludedSlot = '';
|
||
}
|
||
if (!openedRequestedWeapon && data.requestedWeaponId) {
|
||
const requested = data.weapons.find((weapon) => weapon.id === data.requestedWeaponId);
|
||
if (requested) edit(requested);
|
||
openedRequestedWeapon = true;
|
||
}
|
||
if (form?.weaponSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||
handledResultToken = form.resultToken;
|
||
if (form.weaponMode === 'add') renderResetToken += 1;
|
||
editing = form.weaponMode === 'add' ? blank() : null;
|
||
}
|
||
if (form?.weaponBonusProfileSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||
handledResultToken = form.resultToken;
|
||
bonusEditing = null;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<svelte:head><title>Weapons — SHD.build admin</title></svelte:head>
|
||
<div class="module-toolbar"><div><p class="eyebrow">ARMAMENT DEFINITION REGISTRY</p><h2>WEAPONS</h2><p>Configure shared category bonuses, fixed handling, tier progression, talent pools, and tactical augmentations.</p></div><button class="primary" onclick={() => { editing = blank(); bonusEditing = null; }}>ADD WEAPON <span>+</span></button></div>
|
||
<details class="gear-augment-registry weapon-bonus-registry">
|
||
<summary class="selection-head"><div><span>SHARED WEAPON CATEGORY BONUSES</span><small>INHERITED BY STANDARD AND EXOTIC WEAPONS</small></div><div><b>{data.bonusProfiles.filter((profile) => profile.effects.length === 3).length} / 6 CONFIGURED</b><i aria-hidden="true">+</i></div></summary>
|
||
<div class="gear-augment-profile-grid weapon-bonus-profile-grid">
|
||
{#each data.bonusProfiles as profile}
|
||
<button type="button" class:configured={profile.effects.length === 3} onclick={() => editBonusProfile(profile)}>
|
||
<header><div><strong>{profile.category}</strong><small>BASE WEAPON TYPE</small></div><span>{profile.effects.length === 3 ? 'CONFIGURED' : 'REQUIRED'}</span></header>
|
||
<div>{#each profile.effects as effect}<span><small>0{effect.sort_order}</small><b>{effect.attribute_name}</b><em>{effect.value}{effect.unit === 'flat' ? '' : effect.unit}</em></span>{/each}</div>
|
||
</button>
|
||
{/each}
|
||
</div>
|
||
</details>
|
||
{#if bonusEditing}
|
||
<div class="dashboard-editor weapon-bonus-editor">
|
||
<div class="editor-title"><div><p class="eyebrow">SHARED CATEGORY BONUSES</p><h3>{bonusEditing.category}</h3></div><button aria-label="Close weapon category bonus editor" onclick={() => bonusEditing = null}>×</button></div>
|
||
<PendingForm action="?/saveBonusProfile">
|
||
<input type="hidden" name="category" value={bonusEditing.category}/>
|
||
<p class="console-note">Every {bonusEditing.category} inherits these bonuses, including exotic weapons in this category.</p>
|
||
<div class="weapon-bonus-fields">
|
||
{#each bonusEditing.effects as effect, index}
|
||
<label class="field">{effect.attribute_name}<input name={`bonusValue${index + 1}`} type="number" min="0.01" step="any" bind:value={effect.value} required/></label>
|
||
{/each}
|
||
</div>
|
||
{#if form?.bonusProfileError}<p class="error">{form.bonusProfileError}</p>{/if}
|
||
<div class="form-actions"><button type="button" class="secondary" onclick={() => bonusEditing = null}>CANCEL</button><button class="primary">SAVE CATEGORY BONUSES →</button></div>
|
||
</PendingForm>
|
||
</div>
|
||
{/if}
|
||
<KpiFilters active={data.filter} items={[{value:'',label:'TOTAL WEAPONS',count:data.summary.total,description:'Show all definitions'},{value:'active',label:'ACTIVE',count:data.summary.active,description:'Available to builder'},{value:'standard',label:'STANDARD',count:data.summary.standard,description:'Non-exotic weapons'},{value:'exotic',label:'EXOTIC',count:data.summary.exotic,description:'Exotic weapons'}]}/>
|
||
|
||
{#snippet weaponEditor()}
|
||
<div class="dashboard-editor weapon-editor">
|
||
<div class="editor-title"><div><p class="eyebrow">{editing.id ? 'MODIFY WEAPON' : 'NEW WEAPON'}</p><h3>{editing.id ? editing.name : 'WEAPON DEFINITION'}</h3></div><button aria-label="Close editor" onclick={() => editing = null}>×</button></div>
|
||
<PendingForm action="?/save" enctype="multipart/form-data">
|
||
<input type="hidden" name="id" value={editing.id}/>
|
||
<input type="hidden" name="modSlots" value={JSON.stringify(modSlotPayload())}/>
|
||
<div class="weapon-identity-grid">
|
||
<label class="field weapon-name">NAME<input name="name" bind:value={editing.name} maxlength="80" required/></label>
|
||
<SegmentedChoice legend="CATEGORY" name="category" options={categoryOptions} bind:value={editing.category} onchange={(values) => setCategory(values[0])} required/>
|
||
<SegmentedChoice legend="NATIVE TIER" name="nativeTier" options={tierOptions} bind:value={editing.native_tier} required/>
|
||
<SegmentedChoice legend="DAMAGE TYPE" name="damageType" options={damageTypeOptions} bind:value={editing.damage_type} required/>
|
||
<label class="field exotic-toggle">EXOTIC STATUS<span title="Exotic weapon"><input name="isExotic" type="checkbox" value="1" checked={Number(editing.is_exotic) === 1} onchange={(event) => setExotic(event.currentTarget.checked)}/><GameIcon name="exotic" decorative/><b class="sr-only">EXOTIC WEAPON</b></span></label>
|
||
</div>
|
||
<GameDataRenderUploadField currentUrl={editing.renderUrl} label="WEAPON RENDER" resetToken={renderResetToken}/>
|
||
{#if Number(editing.is_exotic)}
|
||
<section class="weapon-exotic-talent">
|
||
<div class="selection-head"><div><span>DEDICATED EXOTIC TALENT</span><small>SELECT ONE TALENT FROM THE EXOTIC WEAPON REGISTRY</small></div><b>{editing.exotic_talent_id ? '1 / 1 ASSIGNED' : 'REQUIRED'}</b></div>
|
||
<SearchableAssignment
|
||
items={exoticTalentOptions()}
|
||
fieldName="exoticTalentId"
|
||
mode="single"
|
||
subjectLabel="EXOTIC TALENT"
|
||
searchPlaceholder="Search exotic talents…"
|
||
emptySelectionLabel="NO EXOTIC TALENT ASSIGNED"
|
||
bind:value={editing.exotic_talent_id}
|
||
/>
|
||
<p class="weapon-mod-slot-note"><b>REFERENCE ONLY</b><span>Create and model exotic talents in the Talent registry. An exotic talent can belong to one weapon, and Tier II may remain not yet documented.</span></p>
|
||
{#if data.exoticTalentOptions.length === 0}<p class="error">Create an active Exotic Weapon talent in the Talent registry before saving this weapon.</p>{/if}
|
||
</section>
|
||
{/if}
|
||
<div class="selection-head"><div><span>FIXED WEAPON ATTRIBUTES</span><small>UNCHANGED ACROSS TIERS</small></div><b>BASE HANDLING</b></div>
|
||
<div class="weapon-fixed-grid">
|
||
<label class="field">RPM<input name="rpm" type="number" min="0" step="1" bind:value={editing.rpm} required/></label>
|
||
<label class="field">MAGAZINE<input name="magazine" type="number" min="0" step="1" bind:value={editing.magazine} required/></label>
|
||
<label class="field">RELOAD SPEED<input name="reloadSpeed" type="number" min="0" step="any" bind:value={editing.reload_speed} required/></label>
|
||
<label class="field">ACCURACY<input name="accuracy" type="number" min="0" step="any" bind:value={editing.accuracy} required/></label>
|
||
<label class="field">STABILITY<input name="stability" type="number" min="0" step="any" bind:value={editing.stability} required/></label>
|
||
</div>
|
||
<div class="selection-head"><div><span>TIER PROGRESSION</span><small>DAMAGE RANGES · CORE ATTRIBUTES USE GLOBAL CONFIGURATION</small></div><b>3 TIERS REQUIRED</b></div>
|
||
<p class="weapon-mod-slot-note"><b>MOD SLOTS OPTIONAL</b><span>T1 and T2 share one slot set. T0 uses that set minus one slot. ISAC marks the weapon Config Required until you can verify both parts.</span></p>
|
||
<div class="weapon-tier-grid">
|
||
{#each [0, 1, 2] as tier}
|
||
<fieldset class="weapon-tier-card" class:unavailable={tier < Number(editing.native_tier)} disabled={tier < Number(editing.native_tier)}><legend>TIER {tier}</legend>
|
||
{#if tier < Number(editing.native_tier)}<p class="tier-unavailable">BELOW NATIVE TIER · NOT AVAILABLE</p>{/if}
|
||
<div class="weapon-range-row"><strong><GameIcon name={iconName(editing.damage_type)} decorative/><span>DMG<small>{editing.damage_type.toUpperCase()} DAMAGE</small></span></strong><label>MIN<input name={`tier${tier}damageMin`} type="number" min="0" step="any" value={tierAt(tier).damage_min} oninput={(event) => tierAt(tier).damage_min = event.currentTarget.valueAsNumber} required/></label><label>MAX<input name={`tier${tier}damageMax`} type="number" min="0" step="any" value={tierAt(tier).damage_max} oninput={(event) => tierAt(tier).damage_max = event.currentTarget.valueAsNumber} required/></label></div>
|
||
</fieldset>
|
||
{/each}
|
||
</div>
|
||
<section class="weapon-mod-slot-config">
|
||
<div class="selection-head"><div><span>{Number(editing.native_tier) === 2 ? 'T2 WEAPON MOD SLOTS' : 'SHARED T1 / T2 WEAPON MOD SLOTS'}</span><small>SELECT UP TO FIVE VERIFIED SLOTS</small></div><b class:config-required={editing.sharedModSlots.length !== 5}>{editing.sharedModSlots.length} / 5</b></div>
|
||
<SearchableAssignment items={data.modSlotOptions} presentation="toggle-grid" subjectLabel="SHARED MOD SLOTS" bind:selected={editing.sharedModSlots}/>
|
||
{#if Number(editing.native_tier) === 0}
|
||
<div class="weapon-t0-exclusion">
|
||
<div class="selection-head"><div><span>T0 SLOT NOT PRESENT</span><small>CHOOSE THE ONE SHARED SLOT OMITTED AT T0</small></div><b class:config-required={!editing.t0ExcludedSlot}>{editing.t0ExcludedSlot ? `${t0SlotCount()} / 4` : 'NOT VERIFIED'}</b></div>
|
||
<SegmentedChoice legend="T0 EXCLUSION" name="t0ExcludedSlot" options={[{value:'',label:'NOT VERIFIED'}, ...editing.sharedModSlots.map((slot: string) => ({value:slot,label:slot}))]} columns={Math.min(3, editing.sharedModSlots.length + 1)} bind:value={editing.t0ExcludedSlot}/>
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
<div class="weapon-talent-pools">
|
||
<div><div class="selection-head"><div><span>TALENT 1 · TOP POOL</span><small>GLOBALLY CLASSIFIED FOR FIRST SLOT</small></div><b>{editing.talent1Ids.length}</b></div><SearchableAssignment items={data.talent1Options} presentation="visual-grid" fieldName="talent1Ids" bind:selected={editing.talent1Ids}/></div>
|
||
<div><div class="selection-head"><div><span>TALENT 2 · BOTTOM POOL</span><small>GLOBALLY CLASSIFIED FOR SECOND SLOT</small></div><b>{editing.talent2Ids.length}</b></div><SearchableAssignment items={data.talent2Options} presentation="visual-grid" fieldName="talent2Ids" bind:selected={editing.talent2Ids}/></div>
|
||
</div>
|
||
<div class="selection-head"><div><span>TACTICAL AUGMENTATION 01</span><small>ONE OR TWO UNIQUE ATTRIBUTE UPGRADES</small></div><button type="button" class="row-action" onclick={addAugmentEffect} disabled={editing.augmentEffects.length >= 2}>ADD EFFECT +</button></div>
|
||
<div class="weapon-augment-effects">
|
||
{#each editing.augmentEffects as effect: any, index}
|
||
<div class="augment-effect"><strong>0{index + 1}</strong><div class="field"><span>ATTRIBUTE</span><SearchableAssignment items={data.stats} fieldName={`augmentStat${index + 1}`} mode="single" compact subjectLabel="ATTRIBUTE" searchPlaceholder="Search attributes…" bind:value={effect.stat_attribute_id}/></div><label class="field">VALUE<input name={`augmentValue${index + 1}`} type="number" min="0" step="any" bind:value={effect.value} required/></label>{#if index > 0}<button type="button" aria-label="Remove second augmentation effect" onclick={() => removeAugmentEffect(index)}>×</button>{/if}</div>
|
||
{/each}
|
||
</div>
|
||
<div class="augment-sequence"><div><b>02</b><span>+20% DAMAGE</span></div><div><b>03</b><span>+40% DAMAGE</span></div><div><b>04</b><span>+60% DAMAGE</span></div><div><b>05</b><span>+80% DAMAGE</span></div>{#if Number(editing.is_exotic)}<div class="exotic-step"><b>06</b><span>EXOTIC TALENT → TIER II</span></div>{/if}</div>
|
||
{#if data.talent1Options.length === 0 || data.talent2Options.length === 0}<p class="error">Create and classify at least one active Talent 1 and Talent 2 weapon talent before adding weapons.</p>{/if}
|
||
{#if data.stats.length === 0}<p class="error">Create an active attribute before configuring tactical augmentation 01.</p>{/if}
|
||
{#if form?.weaponError}<p class="error">{form.weaponError}</p>{/if}
|
||
<div class="form-actions split-actions"><div>{#if editing.id}<button class="moderation-action danger weapon-archive-action" formaction="?/archive" name="id" value={editing.id}><b aria-hidden="true">⌁</b> ARCHIVE WEAPON</button>{/if}</div><div><button type="button" class="secondary" onclick={() => editing = null}>CANCEL</button><button class="primary" disabled={data.talent1Options.length === 0 || data.talent2Options.length === 0 || data.stats.length === 0 || (Number(editing.is_exotic) === 1 && !editing.exotic_talent_id)}>SAVE WEAPON →</button></div></div>
|
||
</PendingForm>
|
||
</div>
|
||
{/snippet}
|
||
|
||
{#if editing && !editing.id}
|
||
{@render weaponEditor()}
|
||
{/if}
|
||
|
||
<RegistryPagination pagination={data.pagination} search={data.search}/>
|
||
<div class="dashboard-table weapon-dashboard-table">
|
||
<div class="dashboard-row weapon-row table-heading"><SortableColumnHeader label="WEAPON" column="name" sort={data.sort}/><SortableColumnHeader label="CLASS" column="class" sort={data.sort}/><SortableColumnHeader label="FIXED ATTRIBUTES" column="fixed" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="TIER RANGE" column="tiers" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="TALENT POOLS" column="talents" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="AUGMENTS" column="augments" sort={data.sort} defaultDirection="desc"/><SortableColumnHeader label="STATE" column="state" sort={data.sort} defaultDirection="desc"/><span></span></div>
|
||
{#each data.weapons as weapon}
|
||
<div class="dashboard-row weapon-row" class:archived={Number(weapon.active) !== 1} class:pending-record={weapon.pending} class:registry-row-editing={editing?.id === weapon.id}><div class="registry-render-identity">{#if weapon.renderUrl}<img class="registry-render" src={weapon.renderUrl} alt=""/>{/if}<div><strong>{weapon.name}</strong><small class="weapon-damage-label">{#if weapon.pending}PROPOSED BY {weapon.proposerName}{:else}<GameIcon name={iconName(weapon.damage_type)} decorative/>{Number(weapon.is_exotic) ? 'EXOTIC · ' : ''}{weapon.damage_type.toUpperCase()} DAMAGE · {weapon.baseBonuses.length} BASE BONUSES{/if}</small></div></div><span class="weapon-category-label">{#if Number(weapon.is_exotic)}<GameIcon name="exotic" decorative/>{/if}<GameIcon name={iconName(weapon.category)} decorative/><span>{weapon.category} · T{weapon.native_tier}</span></span><span>{weapon.rpm} RPM · {weapon.magazine} MAG<small> · {weapon.reload_speed} RELOAD</small></span><span>{weapon.tiers.length}/{3 - Number(weapon.native_tier)} CONFIGURED<small> · {weapon.modSlots.reduce((total: number,row: any) => total + row.slots.length,0)} MOD SLOTS</small></span><span title={`T1: ${weapon.talent1Ids.map(talentLabel).join(', ')} · T2: ${weapon.talent2Ids.map(talentLabel).join(', ')}`}>{weapon.talent1Ids.length} / {weapon.talent2Ids.length}</span><span title={weapon.augmentEffects.map((effect: any) => `${statLabel(String(effect.stat_attribute_id))} +${effect.value}`).join(', ')}>{Number(weapon.is_exotic) ? '6' : '5'} SLOTS</span><b class:pending={weapon.pending} class:config-required={weaponState(weapon) === 'CONFIG REQUIRED'} class="state-dot">{weaponState(weapon)}</b>{#if weapon.pending}<span></span>{:else}<button onclick={() => edit(weapon)} aria-expanded={editing?.id === weapon.id} aria-controls={`weapon-editor-${weapon.id}`}>EDIT</button>{/if}</div>
|
||
{#if editing?.id === weapon.id}
|
||
<RegistryInlineEditor id={`weapon-editor-${weapon.id}`} label={`Edit ${weapon.name}`}>
|
||
{@render weaponEditor()}
|
||
</RegistryInlineEditor>
|
||
{/if}
|
||
{:else}<div class="empty-dashboard"><strong>NO WEAPONS REGISTERED</strong><span>Add active weapon talents and attributes, then create the first armament definition.</span></div>{/each}
|
||
</div>
|