feat(isac): add specialization data management
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<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])}
|
||||
showIndicator={false}
|
||||
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} showIndicator={false}/>
|
||||
</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>
|
||||
Reference in New Issue
Block a user