Simplify tiered talent editing
This commit is contained in:
@@ -1,20 +1,68 @@
|
||||
<script lang="ts">
|
||||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||||
let {data,form}=$props();
|
||||
type Effect={statId:string;value:string};
|
||||
let talent=$state<any>(null),trigger=$state<any>(null),uptime=$state(false);
|
||||
const blankTalent=()=>({id:'',name:'',category:'Weapon',I:{description:'',triggerIds:[],effects:[{statId:'',value:''}]},II:{description:'',triggerIds:[],effects:[{statId:'',value:''}]}});
|
||||
const editTalent=(row:any)=>{const tier=(name:string)=>row.tiers.find((item:any)=>item.tier===name)??{description:'',triggerIds:[],effects:[]};const one=tier('I'),two=tier('II');talent={...row,I:{description:one.description,triggerIds:[...one.triggerIds],effects:one.effects.map((e:any)=>({statId:String(e.stat_attribute_id),value:String(e.value)}))},II:{description:two.description,triggerIds:[...two.triggerIds],effects:two.effects.map((e:any)=>({statId:String(e.stat_attribute_id),value:String(e.value)}))}}};
|
||||
const blankTrigger=()=>{uptime=false;trigger={id:'',name:'',description:'',min_uptime:'',max_uptime:''}};
|
||||
const editTrigger=(row:any)=>{uptime=Number(row.supports_uptime)===1;trigger={...row}};
|
||||
const toggle=(tier:'I'|'II',id:string,checked:boolean)=>talent[tier].triggerIds=checked?[...talent[tier].triggerIds,id]:talent[tier].triggerIds.filter((value:string)=>value!==id);
|
||||
const addEffect=(tier:'I'|'II')=>talent[tier].effects=[...talent[tier].effects,{statId:'',value:''}];
|
||||
const removeEffect=(tier:'I'|'II',index:number)=>talent[tier].effects=talent[tier].effects.filter((_:Effect,row:number)=>row!==index);
|
||||
const configured=(tiers:Array<{tier:unknown}>,name:string)=>tiers.some((item)=>item.tier===name);
|
||||
let { data, form } = $props();
|
||||
type Effect = { statId: string; valueI: string; valueII: string };
|
||||
let talent = $state<any>(null), trigger = $state<any>(null), uptime = $state(false);
|
||||
const blankTalent = () => ({ id: '', name: '', category: 'Weapon', descriptionTemplate: '', tierIDescription: '', tierIIDescription: '', triggerIds: [], effects: [{ statId: '', valueI: '', valueII: '' }] });
|
||||
const editTalent = (row: any) => {
|
||||
const tier = (name: string) => row.tiers.find((item: any) => item.tier === name) ?? { description: '', triggerIds: [], effects: [] };
|
||||
const one = tier('I'), two = tier('II');
|
||||
const statIds = [...new Set([...one.effects, ...two.effects].map((effect: any) => String(effect.stat_attribute_id)))];
|
||||
talent = {
|
||||
...row,
|
||||
descriptionTemplate: row.descriptionTemplate || one.description,
|
||||
tierIDescription: one.description,
|
||||
tierIIDescription: two.description,
|
||||
triggerIds: [...new Set([...one.triggerIds, ...two.triggerIds])],
|
||||
effects: statIds.map((statId) => ({
|
||||
statId,
|
||||
valueI: String(one.effects.find((effect: any) => String(effect.stat_attribute_id) === statId)?.value ?? ''),
|
||||
valueII: String(two.effects.find((effect: any) => String(effect.stat_attribute_id) === statId)?.value ?? '')
|
||||
}))
|
||||
};
|
||||
};
|
||||
const blankTrigger = () => { uptime = false; trigger = { id: '', name: '', description: '', min_uptime: '', max_uptime: '' }; };
|
||||
const editTrigger = (row: any) => { uptime = Number(row.supports_uptime) === 1; trigger = { ...row }; };
|
||||
const toggleTrigger = (id: string, checked: boolean) => talent.triggerIds = checked ? [...talent.triggerIds, id] : talent.triggerIds.filter((value: string) => value !== id);
|
||||
const addEffect = () => talent.effects = [...talent.effects, { statId: '', valueI: '', valueII: '' }];
|
||||
const removeEffect = (index: number) => talent.effects = talent.effects.filter((_: Effect, row: number) => row !== index);
|
||||
const configured = (tiers: Array<{ tier: unknown }>, name: string) => tiers.some((item) => item.tier === name);
|
||||
const displayValue = (effect: Effect, tier: 'I' | 'II') => {
|
||||
const stat = data.stats.find((item) => item.id === effect.statId);
|
||||
const value = tier === 'I' ? effect.valueI : effect.valueII;
|
||||
const unit = stat?.unit === 'flat' ? '' : stat?.unit ?? '';
|
||||
return unit === 'Shots' ? `${value} ${unit}` : `${value}${unit}`;
|
||||
};
|
||||
const preview = (tier: 'I' | 'II') => talent.descriptionTemplate.replace(/\{(\d+)\}/g, (placeholder: string, rawIndex: string) => {
|
||||
const effect = talent.effects[Number(rawIndex) - 1];
|
||||
return effect ? displayValue(effect, tier) : placeholder;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Talents & triggers — SHD.build admin</title></svelte:head>
|
||||
<div class="module-toolbar"><div><p class="eyebrow">TALENT REGISTRY</p><h2>TALENTS & TRIGGERS</h2><p>Tier behavior, reusable activation conditions, uptime, and attribute effects.</p></div><div><button class="secondary" onclick={blankTrigger}>ADD TRIGGER +</button><button class="primary" onclick={()=>talent=blankTalent()}>ADD TALENT +</button></div></div>
|
||||
<div class="module-kpis"><div><span>TALENTS</span><strong>{data.talents.length}</strong><small>Registered definitions</small></div><div><span>TRIGGERS</span><strong>{data.triggers.length}</strong><small>Reusable conditions</small></div><div><span>WEAPON</span><strong>{data.talents.filter((item)=>item.category==='Weapon').length}</strong><small>Weapon talents</small></div><div><span>GEAR</span><strong>{data.talents.filter((item)=>item.category!=='Weapon').length}</strong><small>Body armor and backpack</small></div></div>
|
||||
{#if trigger}<div class="dashboard-editor"><div class="editor-title"><div><p class="eyebrow">{trigger.id?'MODIFY TRIGGER':'NEW TRIGGER'}</p><h3>{trigger.id?trigger.name:'TRIGGER DEFINITION'}</h3></div><button aria-label="Close trigger editor" onclick={()=>trigger=null}>×</button></div><PendingForm action="?/saveTrigger"><input type="hidden" name="id" value={trigger.id}/><div class="form-grid"><label class="field">NAME<input name="name" bind:value={trigger.name} required/></label><label class="field">DESCRIPTION<textarea name="description" bind:value={trigger.description} required></textarea></label><label class="field uptime-field"><span><input name="supportsUptime" type="checkbox" bind:checked={uptime}/> ENABLE THEORETICAL UPTIME</span><div><input name="minUptime" type="number" min="0" max="100" bind:value={trigger.min_uptime} disabled={!uptime} placeholder="MIN %"/><input name="maxUptime" type="number" min="0" max="100" bind:value={trigger.max_uptime} disabled={!uptime} placeholder="MAX %"/></div></label><p class="script-note"><b>CALCULATION SCRIPT</b><span>Reserved for the future calculation engine</span></p></div>{#if form?.triggerError}<p class="error">{form.triggerError}</p>{/if}<div class="form-actions"><button type="button" class="secondary" onclick={()=>trigger=null}>CANCEL</button><button class="primary">SAVE TRIGGER →</button></div></PendingForm></div>{/if}
|
||||
{#if talent}<div class="dashboard-editor"><div class="editor-title"><div><p class="eyebrow">{talent.id?'MODIFY TALENT':'NEW TALENT'}</p><h3>{talent.id?talent.name:'TALENT DEFINITION'}</h3></div><button aria-label="Close talent editor" onclick={()=>talent=null}>×</button></div><PendingForm action="?/saveTalent"><input type="hidden" name="id" value={talent.id}/><input type="hidden" name="tierIEffects" value={JSON.stringify(talent.I.effects)}/><input type="hidden" name="tierIIEffects" value={JSON.stringify(talent.II.effects)}/><div class="form-grid talent-identity"><label class="field">NAME<input name="name" bind:value={talent.name} required/></label><label class="field">CATEGORY<select name="category" bind:value={talent.category}><option>Weapon</option><option>Backpack</option><option>Body Armor</option></select></label></div><div class="tier-grid">{#each ['I','II'] as tier}<fieldset class="tier-card"><legend>TIER {tier}</legend><label class="field">DISPLAY DESCRIPTION<textarea name={tier==='I'?'tierIDescription':'tierIIDescription'} bind:value={talent[tier].description} required></textarea></label><p class="label">ACTIVATION TRIGGERS</p><div class="trigger-list">{#each data.triggers.filter((item)=>Number(item.active)===1) as item}<div><label><input name={tier==='I'?'tierITriggerIds':'tierIITriggerIds'} type="checkbox" value={item.id} checked={talent[tier].triggerIds.includes(String(item.id))} onchange={(event)=>toggle(tier as 'I'|'II',String(item.id),event.currentTarget.checked)}/> <span>{item.name}</span></label><button type="button" onclick={()=>editTrigger(item)}>EDIT</button></div>{/each}</div><div class="effects-head"><p class="label">ATTRIBUTE EFFECTS</p><button type="button" onclick={()=>addEffect(tier as 'I'|'II')}>ADD EFFECT +</button></div>{#each talent[tier].effects as effect,index}<div class="talent-effect"><select bind:value={effect.statId} required><option value="">Select attribute…</option>{#each data.stats as stat}<option value={stat.id}>{stat.name}</option>{/each}</select><input type="number" step="any" bind:value={effect.value} placeholder="VALUE" required/><button type="button" onclick={()=>removeEffect(tier as 'I'|'II',index)}>×</button></div>{/each}</fieldset>{/each}</div>{#if form?.talentError}<p class="error">{form.talentError}</p>{/if}<div class="form-actions"><button type="button" class="secondary" onclick={()=>talent=null}>CANCEL</button><button class="primary">SAVE TALENT →</button></div></PendingForm></div>{/if}
|
||||
<div class="dashboard-table talent-dashboard-table"><div class="dashboard-row table-heading"><span>TALENT</span><span>CATEGORY</span><span>TIER I</span><span>TIER II</span><span>STATE</span><span></span></div>{#each data.talents as item}<div class="dashboard-row"><div><strong>{item.name}</strong><small>Tiered talent</small></div><span>{item.category}</span><span class:configured={configured(item.tiers,'I')}>{configured(item.tiers,'I')?'CONFIGURED':'MISSING'}</span><span class:configured={configured(item.tiers,'II')}>{configured(item.tiers,'II')?'CONFIGURED':'MISSING'}</span><b class="state-dot">ACTIVE</b><button onclick={()=>editTalent(item)}>EDIT</button></div>{/each}</div>
|
||||
<div class="module-toolbar"><div><p class="eyebrow">TALENT REGISTRY</p><h2>TALENTS & TRIGGERS</h2><p>Shared behavior with tier-specific values, reusable triggers, and attribute effects.</p></div><div><button class="secondary" onclick={blankTrigger}>ADD TRIGGER +</button><button class="primary" onclick={() => talent = blankTalent()}>ADD TALENT +</button></div></div>
|
||||
<div class="module-kpis"><div><span>TALENTS</span><strong>{data.talents.length}</strong><small>Registered definitions</small></div><div><span>TRIGGERS</span><strong>{data.triggers.length}</strong><small>Reusable conditions</small></div><div><span>WEAPON</span><strong>{data.talents.filter((item) => item.category === 'Weapon').length}</strong><small>Weapon talents</small></div><div><span>GEAR</span><strong>{data.talents.filter((item) => item.category !== 'Weapon').length}</strong><small>Body armor and backpack</small></div></div>
|
||||
|
||||
{#if trigger}
|
||||
<div class="dashboard-editor"><div class="editor-title"><div><p class="eyebrow">{trigger.id ? 'MODIFY TRIGGER' : 'NEW TRIGGER'}</p><h3>{trigger.id ? trigger.name : 'TRIGGER DEFINITION'}</h3></div><button aria-label="Close trigger editor" onclick={() => trigger = null}>×</button></div><PendingForm action="?/saveTrigger"><input type="hidden" name="id" value={trigger.id}/><div class="form-grid"><label class="field">NAME<input name="name" bind:value={trigger.name} required/></label><label class="field">DESCRIPTION<textarea name="description" bind:value={trigger.description} required></textarea></label><label class="field uptime-field"><span><input name="supportsUptime" type="checkbox" bind:checked={uptime}/> ENABLE THEORETICAL UPTIME</span><div><input name="minUptime" type="number" min="0" max="100" bind:value={trigger.min_uptime} disabled={!uptime} placeholder="MIN %"/><input name="maxUptime" type="number" min="0" max="100" bind:value={trigger.max_uptime} disabled={!uptime} placeholder="MAX %"/></div></label><p class="script-note"><b>CALCULATION SCRIPT</b><span>Reserved for the future calculation engine</span></p></div>{#if form?.triggerError}<p class="error">{form.triggerError}</p>{/if}<div class="form-actions"><button type="button" class="secondary" onclick={() => trigger = null}>CANCEL</button><button class="primary">SAVE TRIGGER →</button></div></PendingForm></div>
|
||||
{/if}
|
||||
|
||||
{#if talent}
|
||||
<div class="dashboard-editor">
|
||||
<div class="editor-title"><div><p class="eyebrow">{talent.id ? 'MODIFY TALENT' : 'NEW TALENT'}</p><h3>{talent.id ? talent.name : 'TALENT DEFINITION'}</h3></div><button aria-label="Close talent editor" onclick={() => talent = null}>×</button></div>
|
||||
<PendingForm action="?/saveTalent">
|
||||
<input type="hidden" name="id" value={talent.id}/><input type="hidden" name="effects" value={JSON.stringify(talent.effects)}/>
|
||||
<div class="form-grid talent-identity"><label class="field">NAME<input name="name" bind:value={talent.name} required/></label><label class="field">CATEGORY<select name="category" bind:value={talent.category}><option>Weapon</option><option>Backpack</option><option>Body Armor</option></select></label></div>
|
||||
<label class="field template-field">SHARED DESCRIPTION TEMPLATE<textarea name="descriptionTemplate" bind:value={talent.descriptionTemplate} placeholder="Hitting an enemy {1} times grants +{2}..." required></textarea><small>Use {'{1}'}, {'{2}'}, and so on to insert each effect value with its unit.</small></label>
|
||||
<div class="tier-preview"><div><span>TIER I PREVIEW</span><p>{preview('I') || 'Description preview'}</p></div><div><span>TIER II PREVIEW</span><p>{preview('II') || 'Description preview'}</p></div></div>
|
||||
<p class="label">SHARED ACTIVATION TRIGGERS</p>
|
||||
<div class="trigger-list shared-trigger-list">{#each data.triggers.filter((item) => Number(item.active) === 1) as item}<div><label><input name="triggerIds" type="checkbox" value={item.id} checked={talent.triggerIds.includes(String(item.id))} onchange={(event) => toggleTrigger(String(item.id), event.currentTarget.checked)}/> <span>{item.name}</span></label><button type="button" onclick={() => editTrigger(item)}>EDIT</button></div>{/each}</div>
|
||||
<div class="effects-head"><p class="label">SHARED ATTRIBUTE EFFECTS · TIER VALUES</p><button type="button" onclick={addEffect}>ADD EFFECT +</button></div>
|
||||
<div class="shared-effects"><div class="shared-effect effect-heading"><span>ATTRIBUTE</span><span>TIER I VALUE</span><span>TIER II VALUE</span><span></span></div>{#each talent.effects as effect, index}<div class="shared-effect"><select bind:value={effect.statId} required><option value="">Select attribute…</option>{#each data.stats as stat}<option value={stat.id}>{stat.name} ({stat.unit || 'no unit'})</option>{/each}</select><input type="number" step="any" bind:value={effect.valueI} placeholder="TIER I" required/><input type="number" step="any" bind:value={effect.valueII} placeholder="TIER II" required/><button type="button" aria-label={`Remove effect ${index + 1}`} onclick={() => removeEffect(index)}>×</button></div>{/each}</div>
|
||||
{#if form?.talentError}<p class="error">{form.talentError}</p>{/if}
|
||||
<div class="form-actions"><button type="button" class="secondary" onclick={() => talent = null}>CANCEL</button><button class="primary">SAVE TALENT →</button></div>
|
||||
</PendingForm>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="dashboard-table talent-dashboard-table"><div class="dashboard-row table-heading"><span>TALENT</span><span>CATEGORY</span><span>TIER I</span><span>TIER II</span><span>STATE</span><span></span></div>{#each data.talents as item}<div class="dashboard-row"><div><strong>{item.name}</strong><small>Tiered talent</small></div><span>{item.category}</span><span class:configured={configured(item.tiers, 'I')}>{configured(item.tiers, 'I') ? 'CONFIGURED' : 'MISSING'}</span><span class:configured={configured(item.tiers, 'II')}>{configured(item.tiers, 'II') ? 'CONFIGURED' : 'MISSING'}</span><b class="state-dot">ACTIVE</b><button onclick={() => editTalent(item)}>EDIT</button></div>{/each}</div>
|
||||
|
||||
Reference in New Issue
Block a user