Reset ISAC icon uploads after additions
This commit is contained in:
@@ -62,6 +62,10 @@ All ISAC data-management editors must follow the same successful-save behavior:
|
|||||||
4. Server actions must return the add/edit mode and a unique result token;
|
4. Server actions must return the add/edit mode and a unique result token;
|
||||||
client-side reactive handling must consume each token only once to avoid
|
client-side reactive handling must consume each token only once to avoid
|
||||||
repeated state updates.
|
repeated state updates.
|
||||||
|
5. When a successful add keeps an icon-enabled editor open, increment the
|
||||||
|
shared upload reset token so the native file input, generated preview,
|
||||||
|
filename, drag/paste state, validation message, and removal state are fresh
|
||||||
|
for the next record.
|
||||||
|
|
||||||
Apply this convention whenever an ISAC editor is added or materially changed.
|
Apply this convention whenever an ISAC editor is added or materially changed.
|
||||||
|
|
||||||
|
|||||||
@@ -772,6 +772,12 @@ fixed column to `IconUploadField`, give the record name the flexible dominant
|
|||||||
column, and stack both controls at mobile widths. Do not enlarge an upload
|
column, and stack both controls at mobile widths. Do not enlarge an upload
|
||||||
drop-zone to fill spare identity-row space.
|
drop-zone to fill spare identity-row space.
|
||||||
|
|
||||||
|
Icon-enabled consecutive-entry editors pass a successful-add result token into
|
||||||
|
the shared upload control. Advancing that token clears the native file input,
|
||||||
|
temporary preview URL, filename, drag/paste feedback, and validation state.
|
||||||
|
This reset occurs only after a validated add succeeds; failed saves preserve
|
||||||
|
the selected image, and successful edits close the editor normally.
|
||||||
|
|
||||||
The primary `name` field in every ISAC data editor is a header-level input:
|
The primary `name` field in every ISAC data editor is a header-level input:
|
||||||
46 pixels tall with larger, heavier text. This creates a consistent first
|
46 pixels tall with larger, heavier text. This creates a consistent first
|
||||||
reading point for long registries while keeping secondary metadata compact.
|
reading point for long registries while keeping secondary metadata compact.
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
import ImageUploadInput from '$lib/components/ImageUploadInput.svelte';
|
import ImageUploadInput from '$lib/components/ImageUploadInput.svelte';
|
||||||
let {
|
let {
|
||||||
currentUrl = '',
|
currentUrl = '',
|
||||||
label = 'ICON'
|
label = 'ICON',
|
||||||
}: { currentUrl?: string | null; label?: string } = $props();
|
resetToken = 0
|
||||||
|
}: { currentUrl?: string | null; label?: string; resetToken?: string | number } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset class="icon-upload-field">
|
<fieldset class="icon-upload-field">
|
||||||
<legend>{label}</legend>
|
<legend>{label}</legend>
|
||||||
<ImageUploadInput name="icon" accept="image/svg+xml,image/png,image/webp,image/avif" {currentUrl} emptyLabel="NO ICON" browseLabel={currentUrl ? 'REPLACE ICON' : 'CHOOSE ICON'}/>
|
<ImageUploadInput name="icon" accept="image/svg+xml,image/png,image/webp,image/avif" {currentUrl} {resetToken} emptyLabel="NO ICON" browseLabel={currentUrl ? 'REPLACE ICON' : 'CHOOSE ICON'}/>
|
||||||
{#if currentUrl}<label class="icon-remove"><input name="removeIcon" type="checkbox" value="1"/><span>REMOVE CURRENT ICON</span></label>{/if}
|
{#if currentUrl}<label class="icon-remove"><input name="removeIcon" type="checkbox" value="1"/><span>REMOVE CURRENT ICON</span></label>{/if}
|
||||||
<small>TRANSPARENT SVG, PNG, WEBP, OR AVIF · 2 MiB MAX</small>
|
<small>TRANSPARENT SVG, PNG, WEBP, OR AVIF · 2 MiB MAX</small>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
required = false,
|
required = false,
|
||||||
currentUrl = '',
|
currentUrl = '',
|
||||||
emptyLabel = 'NO IMAGE',
|
emptyLabel = 'NO IMAGE',
|
||||||
browseLabel = 'CHOOSE FILE'
|
browseLabel = 'CHOOSE FILE',
|
||||||
|
resetToken = 0
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
accept: string;
|
accept: string;
|
||||||
@@ -15,16 +16,38 @@
|
|||||||
currentUrl?: string | null;
|
currentUrl?: string | null;
|
||||||
emptyLabel?: string;
|
emptyLabel?: string;
|
||||||
browseLabel?: string;
|
browseLabel?: string;
|
||||||
|
resetToken?: string | number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement | undefined;
|
||||||
let previewUrl = $state('');
|
let previewUrl = $state('');
|
||||||
let fileName = $state('');
|
let fileName = $state('');
|
||||||
let dragging = $state(false);
|
let dragging = $state(false);
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
|
let handledResetToken: string | number | null = null;
|
||||||
|
|
||||||
|
function resetUpload() {
|
||||||
|
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
||||||
|
previewUrl = '';
|
||||||
|
fileName = '';
|
||||||
|
dragging = false;
|
||||||
|
error = '';
|
||||||
|
if (input) input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (handledResetToken === null) {
|
||||||
|
handledResetToken = resetToken;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resetToken !== handledResetToken) {
|
||||||
|
handledResetToken = resetToken;
|
||||||
|
resetUpload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function setFile(file: File | undefined) {
|
function setFile(file: File | undefined) {
|
||||||
if (!file) return;
|
if (!file || !input) return;
|
||||||
if (!file.type.startsWith('image/')) {
|
if (!file.type.startsWith('image/')) {
|
||||||
error = 'Clipboard or dropped content must be an image.';
|
error = 'Clipboard or dropped content must be an image.';
|
||||||
return;
|
return;
|
||||||
@@ -108,7 +131,7 @@
|
|||||||
<span>{fileName || 'OR USE AN UPLOAD METHOD'}</span>
|
<span>{fileName || 'OR USE AN UPLOAD METHOD'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-upload-actions">
|
<div class="image-upload-actions">
|
||||||
<button type="button" class="secondary" onclick={(event) => { event.stopPropagation(); input.click(); }}>{browseLabel}</button>
|
<button type="button" class="secondary" onclick={(event) => { event.stopPropagation(); input?.click(); }}>{browseLabel}</button>
|
||||||
<button type="button" class="secondary" onclick={(event) => { event.stopPropagation(); pasteFromClipboard(); }}>PASTE IMAGE</button>
|
<button type="button" class="secondary" onclick={(event) => { event.stopPropagation(); pasteFromClipboard(); }}>PASTE IMAGE</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
const blank = (): Editor => ({ id: '', name: '', iconUrl: '', active: 1, effects: [2, 3, 4].map((pieces) => ({ pieces_required: pieces, stat_attribute_id: '', value: 0 })) });
|
const blank = (): Editor => ({ id: '', name: '', iconUrl: '', active: 1, effects: [2, 3, 4].map((pieces) => ({ pieces_required: pieces, stat_attribute_id: '', value: 0 })) });
|
||||||
let editing = $state<Editor | null>(null);
|
let editing = $state<Editor | null>(null);
|
||||||
let handledResultToken = $state('');
|
let handledResultToken = $state('');
|
||||||
|
let iconResetToken = $state(0);
|
||||||
const effectFor = (brand: Editor, pieces: number) => brand.effects.find((effect) => Number(effect.pieces_required) === pieces) ?? { pieces_required: pieces, stat_attribute_id: '', value: 0 };
|
const effectFor = (brand: Editor, pieces: number) => brand.effects.find((effect) => Number(effect.pieces_required) === pieces) ?? { pieces_required: pieces, stat_attribute_id: '', value: 0 };
|
||||||
const statFor = (id: string) => data.stats.find((stat) => stat.id === id);
|
const statFor = (id: string) => data.stats.find((stat) => stat.id === id);
|
||||||
const formatEffect = (effect: Effect) => { const stat = statFor(effect.stat_attribute_id); return `${effect.value}${stat?.unit ?? ''} ${stat?.name ?? 'Unknown attribute'}`; };
|
const formatEffect = (effect: Effect) => { const stat = statFor(effect.stat_attribute_id); return `${effect.value}${stat?.unit ?? ''} ${stat?.name ?? 'Unknown attribute'}`; };
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
}
|
}
|
||||||
if (form?.brandSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
if (form?.brandSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||||
handledResultToken = form.resultToken;
|
handledResultToken = form.resultToken;
|
||||||
|
if (form.brandMode === 'add') iconResetToken += 1;
|
||||||
editing = form.brandMode === 'add' ? blank() : null;
|
editing = form.brandMode === 'add' ? blank() : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<PendingForm action="?/save" enctype="multipart/form-data">
|
<PendingForm action="?/save" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="id" value={editing.id} />
|
<input type="hidden" name="id" value={editing.id} />
|
||||||
<div class="brand-editor-body">
|
<div class="brand-editor-body">
|
||||||
<IconUploadField currentUrl={editing.iconUrl} label="BRAND SET ICON"/>
|
<IconUploadField currentUrl={editing.iconUrl} label="BRAND SET ICON" resetToken={iconResetToken}/>
|
||||||
<div>
|
<div>
|
||||||
<label class="field brand-name">BRAND NAME<input name="name" bind:value={editing.name} required maxlength="80" /></label>
|
<label class="field brand-name">BRAND NAME<input name="name" bind:value={editing.name} required maxlength="80" /></label>
|
||||||
<div class="effect-editor">
|
<div class="effect-editor">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
let talentUpgrade = $state(0);
|
let talentUpgrade = $state(0);
|
||||||
let protocolLevel = $state(1);
|
let protocolLevel = $state(1);
|
||||||
let handledResultToken = $state('');
|
let handledResultToken = $state('');
|
||||||
|
let iconResetToken = $state(0);
|
||||||
const qualityClass = (quality: string) => `quality-${quality.toLowerCase().replace('-', '')}`;
|
const qualityClass = (quality: string) => `quality-${quality.toLowerCase().replace('-', '')}`;
|
||||||
const iconName = (type: string): GameIconName => type.toLowerCase() as GameIconName;
|
const iconName = (type: string): GameIconName => type.toLowerCase() as GameIconName;
|
||||||
const qualityOptions = qualities.map((quality) => ({ value: quality, label: quality, className: qualityClass(quality) }));
|
const qualityOptions = qualities.map((quality) => ({ value: quality, label: quality, className: qualityClass(quality) }));
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
}
|
}
|
||||||
if (form?.protocolSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
if (form?.protocolSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||||
handledResultToken = form.resultToken;
|
handledResultToken = form.resultToken;
|
||||||
|
if (form.protocolMode === 'add') iconResetToken += 1;
|
||||||
editing = form.protocolMode === 'add' ? blank() : null;
|
editing = form.protocolMode === 'add' ? blank() : null;
|
||||||
talentUpgrade = 0;
|
talentUpgrade = 0;
|
||||||
protocolLevel = 1;
|
protocolLevel = 1;
|
||||||
@@ -132,7 +134,7 @@
|
|||||||
<input type="hidden" name="id" value={editing.id}/>
|
<input type="hidden" name="id" value={editing.id}/>
|
||||||
<input type="hidden" name="talentEffects" value={JSON.stringify(editing.talent.effects.map((effect: TalentEffect) => ({ statId: effect.stat_attribute_id, baseValue: effect.base_value })))} />
|
<input type="hidden" name="talentEffects" value={JSON.stringify(editing.talent.effects.map((effect: TalentEffect) => ({ statId: effect.stat_attribute_id, baseValue: effect.base_value })))} />
|
||||||
<div class="protocol-identity-grid">
|
<div class="protocol-identity-grid">
|
||||||
<div class="icon-editor-identity"><IconUploadField currentUrl={editing.iconUrl} label="OS PROTOCOL ICON"/><label class="field">PROTOCOL NAME<input name="name" bind:value={editing.name} maxlength="80" required/></label></div>
|
<div class="icon-editor-identity"><IconUploadField currentUrl={editing.iconUrl} label="OS PROTOCOL ICON" resetToken={iconResetToken}/><label class="field">PROTOCOL NAME<input name="name" bind:value={editing.name} maxlength="80" required/></label></div>
|
||||||
<div class="protocol-classification-stack">
|
<div class="protocol-classification-stack">
|
||||||
<SegmentedChoice legend="QUALITY" name="quality" options={qualityOptions} bind:value={editing.quality}/>
|
<SegmentedChoice legend="QUALITY" name="quality" options={qualityOptions} bind:value={editing.quality}/>
|
||||||
<SegmentedChoice legend="TYPE" name="attributeType" options={attributeTypeOptions} bind:value={editing.primary_attribute_type}/>
|
<SegmentedChoice legend="TYPE" name="attributeType" options={attributeTypeOptions} bind:value={editing.primary_attribute_type}/>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
let editing = $state<any | null>(null);
|
let editing = $state<any | null>(null);
|
||||||
let highEndCount = $state('0');
|
let highEndCount = $state('0');
|
||||||
let handledResultToken = $state('');
|
let handledResultToken = $state('');
|
||||||
|
let iconResetToken = $state(0);
|
||||||
const qualitySteps = [
|
const qualitySteps = [
|
||||||
{ value: '0', label: '0 HIGH-END' },
|
{ value: '0', label: '0 HIGH-END' },
|
||||||
{ value: '1', label: '1 HIGH-END' },
|
{ value: '1', label: '1 HIGH-END' },
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (form?.skillChipSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
if (form?.skillChipSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||||
handledResultToken = form.resultToken;
|
handledResultToken = form.resultToken;
|
||||||
|
if (form.skillChipMode === 'add') iconResetToken += 1;
|
||||||
editing = form.skillChipMode === 'add' ? blank() : null;
|
editing = form.skillChipMode === 'add' ? blank() : null;
|
||||||
highEndCount = '0';
|
highEndCount = '0';
|
||||||
}
|
}
|
||||||
@@ -73,7 +75,7 @@
|
|||||||
<PendingForm action="?/save" enctype="multipart/form-data">
|
<PendingForm action="?/save" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="id" value={editing.id}/>
|
<input type="hidden" name="id" value={editing.id}/>
|
||||||
<input type="hidden" name="talentEffects" value={JSON.stringify(editing.talent.effects.map((effect: TalentEffect) => ({ statId: effect.stat_attribute_id, minValue: effect.min_value, maxValue: effect.max_value })))}/>
|
<input type="hidden" name="talentEffects" value={JSON.stringify(editing.talent.effects.map((effect: TalentEffect) => ({ statId: effect.stat_attribute_id, minValue: effect.min_value, maxValue: effect.max_value })))}/>
|
||||||
<div class="skill-chip-identity"><IconUploadField currentUrl={editing.iconUrl} label="SKILL CHIP ICON"/><label class="field">CHIP SET NAME<input name="name" bind:value={editing.name} maxlength="80" required/></label><div class="quality-scope"><span>SUPPORTED QUALITY</span><div><b class="quality-superior">SUPERIOR</b><b class="quality-highend">HIGH-END</b></div><small>Only these qualities participate in chip scaling.</small></div></div>
|
<div class="skill-chip-identity"><IconUploadField currentUrl={editing.iconUrl} label="SKILL CHIP ICON" resetToken={iconResetToken}/><label class="field">CHIP SET NAME<input name="name" bind:value={editing.name} maxlength="80" required/></label><div class="quality-scope"><span>SUPPORTED QUALITY</span><div><b class="quality-superior">SUPERIOR</b><b class="quality-highend">HIGH-END</b></div><small>Only these qualities participate in chip scaling.</small></div></div>
|
||||||
|
|
||||||
<section class="skill-chip-bonus">
|
<section class="skill-chip-bonus">
|
||||||
<div class="selection-head"><div><span>2-PIECE BONUS</span><small>MINIMUM AT ZERO HIGH-END · LINEAR TO MAXIMUM AT THREE HIGH-END</small></div><b>{format(scaled(editing.twoPiece.min_value, editing.twoPiece.max_value), editing.twoPiece.stat_attribute_id)}</b></div>
|
<div class="selection-head"><div><span>2-PIECE BONUS</span><small>MINIMUM AT ZERO HIGH-END · LINEAR TO MAXIMUM AT THREE HIGH-END</small></div><b>{format(scaled(editing.twoPiece.min_value, editing.twoPiece.max_value), editing.twoPiece.stat_attribute_id)}</b></div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
type Effect = { statId: string; valueI: string; valueII: string };
|
type Effect = { statId: string; valueI: string; valueII: string };
|
||||||
let talent = $state<any>(null), trigger = $state<any>(null), uptime = $state(false);
|
let talent = $state<any>(null), trigger = $state<any>(null), uptime = $state(false);
|
||||||
let handledResultToken = $state('');
|
let handledResultToken = $state('');
|
||||||
|
let iconResetToken = $state(0);
|
||||||
const blankTalent = () => ({ id: '', name: '', iconUrl: '', category: 'Weapon Talent 1', descriptionTemplate: '', tierIDescription: '', tierIIDescription: '', triggerIds: [], effects: [{ statId: '', valueI: '', valueII: '' }] });
|
const blankTalent = () => ({ id: '', name: '', iconUrl: '', category: 'Weapon Talent 1', descriptionTemplate: '', tierIDescription: '', tierIIDescription: '', triggerIds: [], effects: [{ statId: '', valueI: '', valueII: '' }] });
|
||||||
const editTalent = (row: any) => {
|
const editTalent = (row: any) => {
|
||||||
const tier = (name: string) => row.tiers.find((item: any) => item.tier === name) ?? { description: '', triggerIds: [], effects: [] };
|
const tier = (name: string) => row.tiers.find((item: any) => item.tier === name) ?? { description: '', triggerIds: [], effects: [] };
|
||||||
@@ -49,7 +50,10 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!form?.resultToken || form.resultToken === handledResultToken) return;
|
if (!form?.resultToken || form.resultToken === handledResultToken) return;
|
||||||
handledResultToken = form.resultToken;
|
handledResultToken = form.resultToken;
|
||||||
if (form.talentSaved) talent = form.talentMode === 'add' ? blankTalent() : null;
|
if (form.talentSaved) {
|
||||||
|
if (form.talentMode === 'add') iconResetToken += 1;
|
||||||
|
talent = form.talentMode === 'add' ? blankTalent() : null;
|
||||||
|
}
|
||||||
if (form.triggerSaved) {
|
if (form.triggerSaved) {
|
||||||
if (form.triggerMode === 'add') blankTrigger();
|
if (form.triggerMode === 'add') blankTrigger();
|
||||||
else trigger = null;
|
else trigger = null;
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
<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>
|
<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" enctype="multipart/form-data">
|
<PendingForm action="?/saveTalent" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="id" value={talent.id}/><input type="hidden" name="effects" value={JSON.stringify(talent.effects)}/>
|
<input type="hidden" name="id" value={talent.id}/><input type="hidden" name="effects" value={JSON.stringify(talent.effects)}/>
|
||||||
<div class="talent-icon-identity"><IconUploadField currentUrl={talent.iconUrl} label="TALENT ICON"/><div class="form-grid talent-identity"><label class="field">NAME<input name="name" bind:value={talent.name} required/></label><label class="field">CATEGORY / SLOT<select name="category" bind:value={talent.category}><option value="Weapon · Slot Unassigned" disabled>Weapon · Choose a slot</option><option>Weapon Talent 1</option><option>Weapon Talent 2</option><option>Backpack</option><option>Body Armor</option></select></label></div></div>
|
<div class="talent-icon-identity"><IconUploadField currentUrl={talent.iconUrl} label="TALENT ICON" resetToken={iconResetToken}/><div class="form-grid talent-identity"><label class="field">NAME<input name="name" bind:value={talent.name} required/></label><label class="field">CATEGORY / SLOT<select name="category" bind:value={talent.category}><option value="Weapon · Slot Unassigned" disabled>Weapon · Choose a slot</option><option>Weapon Talent 1</option><option>Weapon Talent 2</option><option>Backpack</option><option>Body Armor</option></select></label></div></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>
|
<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>
|
<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>
|
<p class="label">SHARED ACTIVATION TRIGGERS</p>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
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: '', exoticTalent: blankExoticTalent(), active: 1, tiers: [blankTier(0), blankTier(1), blankTier(2)], talent1Ids: [], talent2Ids: [], augmentEffects: [{ stat_attribute_id: '', value: 0, sort_order: 1 }], baseBonuses: [{ stat_attribute_id: '', value: 0, sort_order: 1 }] });
|
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: '', exoticTalent: blankExoticTalent(), active: 1, tiers: [blankTier(0), blankTier(1), blankTier(2)], talent1Ids: [], talent2Ids: [], augmentEffects: [{ stat_attribute_id: '', value: 0, sort_order: 1 }], baseBonuses: [{ stat_attribute_id: '', value: 0, sort_order: 1 }] });
|
||||||
let editing = $state<any | null>(null);
|
let editing = $state<any | null>(null);
|
||||||
let handledResultToken = $state('');
|
let handledResultToken = $state('');
|
||||||
|
let iconResetToken = $state(0);
|
||||||
let openedRequestedWeapon = $state(false);
|
let openedRequestedWeapon = $state(false);
|
||||||
const edit = (weapon: any) => {
|
const edit = (weapon: any) => {
|
||||||
const tiers = [0, 1, 2].map((tier) => ({ ...blankTier(tier), ...(weapon.tiers.find((item: any) => Number(item.tier) === tier) ?? {}) }));
|
const tiers = [0, 1, 2].map((tier) => ({ ...blankTier(tier), ...(weapon.tiers.find((item: any) => Number(item.tier) === tier) ?? {}) }));
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
}
|
}
|
||||||
if (form?.weaponSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
if (form?.weaponSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||||
handledResultToken = form.resultToken;
|
handledResultToken = form.resultToken;
|
||||||
|
if (form.weaponMode === 'add') iconResetToken += 1;
|
||||||
editing = form.weaponMode === 'add' ? blank() : null;
|
editing = form.weaponMode === 'add' ? blank() : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,7 +95,7 @@
|
|||||||
<input type="hidden" name="exoticEffects" value={JSON.stringify(editing.exoticTalent.effects)}/>
|
<input type="hidden" name="exoticEffects" value={JSON.stringify(editing.exoticTalent.effects)}/>
|
||||||
<section class="weapon-exotic-talent">
|
<section class="weapon-exotic-talent">
|
||||||
<div class="selection-head"><div><span>DEDICATED EXOTIC TALENT</span><small>OWNED EXCLUSIVELY BY THIS WEAPON · TIER II VALUES REQUIRED</small></div><b>{editing.exoticTalent.id ? 'LINKED RECORD' : 'NEW RECORD'}</b></div>
|
<div class="selection-head"><div><span>DEDICATED EXOTIC TALENT</span><small>OWNED EXCLUSIVELY BY THIS WEAPON · TIER II VALUES REQUIRED</small></div><b>{editing.exoticTalent.id ? 'LINKED RECORD' : 'NEW RECORD'}</b></div>
|
||||||
<div class="exotic-icon-identity"><IconUploadField currentUrl={editing.exoticTalent.iconUrl} label="EXOTIC TALENT ICON"/><div class="form-grid exotic-talent-identity"><label class="field">TALENT NAME<input name="exoticTalentName" bind:value={editing.exoticTalent.name} maxlength="80" required/></label><label class="field">SHARED DESCRIPTION TEMPLATE<textarea name="exoticDescriptionTemplate" bind:value={editing.exoticTalent.descriptionTemplate} placeholder="Hits grant +{1} damage for {2} seconds…" required></textarea><small>Use {'{1}'}, {'{2}'}, and so on for effect values and units.</small></label></div></div>
|
<div class="exotic-icon-identity"><IconUploadField currentUrl={editing.exoticTalent.iconUrl} label="EXOTIC TALENT ICON" resetToken={iconResetToken}/><div class="form-grid exotic-talent-identity"><label class="field">TALENT NAME<input name="exoticTalentName" bind:value={editing.exoticTalent.name} maxlength="80" required/></label><label class="field">SHARED DESCRIPTION TEMPLATE<textarea name="exoticDescriptionTemplate" bind:value={editing.exoticTalent.descriptionTemplate} placeholder="Hits grant +{1} damage for {2} seconds…" required></textarea><small>Use {'{1}'}, {'{2}'}, and so on for effect values and units.</small></label></div></div>
|
||||||
<div class="tier-preview"><div><span>TIER I PREVIEW</span><p>{exoticPreview('I') || 'Description preview'}</p></div><div><span>TIER II PREVIEW</span><p>{exoticPreview('II') || 'Description preview'}</p></div></div>
|
<div class="tier-preview"><div><span>TIER I PREVIEW</span><p>{exoticPreview('I') || 'Description preview'}</p></div><div><span>TIER II PREVIEW</span><p>{exoticPreview('II') || 'Description preview'}</p></div></div>
|
||||||
<div class="weapon-exotic-grid">
|
<div class="weapon-exotic-grid">
|
||||||
<div><div class="selection-head"><div><span>ACTIVATION TRIGGERS</span><small>SHARED BETWEEN BOTH TIERS</small></div><b>{editing.exoticTalent.triggerIds.length}</b></div><SearchableAssignment items={data.triggers} fieldName="exoticTriggerIds" bind:selected={editing.exoticTalent.triggerIds}/></div>
|
<div><div class="selection-head"><div><span>ACTIVATION TRIGGERS</span><small>SHARED BETWEEN BOTH TIERS</small></div><b>{editing.exoticTalent.triggerIds.length}</b></div><SearchableAssignment items={data.triggers} fieldName="exoticTriggerIds" bind:selected={editing.exoticTalent.triggerIds}/></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user