Files
shd.build/src/lib/components/StructuredEffectEditor.svelte
T

102 lines
3.9 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import SearchableAssignment from './SearchableAssignment.svelte';
import SegmentedChoice from './SegmentedChoice.svelte';
type RegistryItem = { id: string; name: string; pending?: boolean; proposerName?: string };
export type StructuredEffect = {
type: 'Attribute' | 'Ability' | 'Custom';
statId: string;
abilityId: string;
label: string;
value: number | string | null;
unit: string;
};
let {
effects = $bindable([]),
attributes,
abilities = [],
idPrefix = 'effect',
emptyLabel = 'NO STRUCTURED EFFECTS',
addLabel = 'ADD EFFECT',
allowTargetLabelFallback = false
}: {
effects?: StructuredEffect[];
attributes: RegistryItem[];
abilities?: RegistryItem[];
idPrefix?: string;
emptyLabel?: string;
addLabel?: string;
allowTargetLabelFallback?: boolean;
} = $props();
const effectTypes = $derived([
{ value: 'Attribute', label: 'ATTRIBUTE' },
...(abilities.length ? [{ value: 'Ability', label: 'ABILITY' }] : []),
{ value: 'Custom', label: 'CUSTOM' }
]);
const units = [
{ value: '', label: 'NONE' },
{ value: '%', label: '%' },
{ value: 's', label: 'SECONDS' },
{ value: 'Shots', label: 'SHOTS' },
{ value: 'm', label: 'METERS' }
];
const blank = (): StructuredEffect => ({
type: 'Custom',
statId: '',
abilityId: '',
label: '',
value: '',
unit: ''
});
const changeType = (index: number, type: string) => {
effects[index] = {
...effects[index],
type: type as StructuredEffect['type'],
statId: type === 'Attribute' ? effects[index].statId : '',
abilityId: type === 'Ability' ? effects[index].abilityId : ''
};
};
</script>
<div class="structured-effect-editor">
{#each effects as effect, index}
{@const labelCanUseTarget = allowTargetLabelFallback && ['Attribute', 'Ability'].includes(effect.type)}
<section class="structured-effect-row">
<header><strong>EFFECT {String(index + 1).padStart(2, '0')}</strong><button type="button" class="danger-link" onclick={() => effects = effects.filter((_, itemIndex) => itemIndex !== index)}>REMOVE</button></header>
<div class="structured-effect-grid">
<SegmentedChoice
legend="EFFECT TYPE"
name={`${idPrefix}-type-${index}`}
options={effectTypes}
bind:value={effect.type}
onchange={(values) => changeType(index, values[0])}
required
/>
{#if effect.type === 'Attribute'}
<div class="field"><span>ATTRIBUTE TARGET</span><SearchableAssignment items={attributes} mode="single" compact subjectLabel="ATTRIBUTE" searchPlaceholder="Search Attributes…" bind:value={effect.statId}/></div>
{:else if effect.type === 'Ability'}
<div class="field"><span>CLASS ABILITY TARGET</span><SearchableAssignment items={abilities} mode="single" compact subjectLabel="CLASS ABILITY" searchPlaceholder="Search abilities…" bind:value={effect.abilityId}/></div>
{:else}
<div class="effect-custom-target"><span>CUSTOM MECHANIC</span><small>Recorded for display now and calculation support later.</small></div>
{/if}
<label class="field effect-label">
EFFECT LABEL{labelCanUseTarget ? ' · OPTIONAL' : ''}
<input
bind:value={effect.label}
maxlength="100"
placeholder={labelCanUseTarget ? 'Uses the selected target name when blank' : 'e.g. Damage Reduction'}
required={!labelCanUseTarget}
/>
</label>
<label class="field">VALUE<input type="number" step="any" bind:value={effect.value} placeholder="OPTIONAL"/></label>
<SegmentedChoice legend="UNIT" name={`${idPrefix}-unit-${index}`} options={units} bind:value={effect.unit}/>
</div>
</section>
{:else}
<div class="structured-effect-empty"><strong>{emptyLabel}</strong><span>Add structured values when this mechanic should contribute to calculations or an ability preview.</span></div>
{/each}
<button type="button" class="secondary structured-effect-add" onclick={() => effects = [...effects, blank()]}>{addLabel} +</button>
</div>