Add persisted and shareable builds
This commit is contained in:
@@ -1,21 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import GameIcon from '$lib/components/GameIcon.svelte';
|
||||
import type { GameIconName } from '$lib/game-icons';
|
||||
import BuilderPicker from '$lib/components/BuilderPicker.svelte';
|
||||
import BuilderRadioGrid from '$lib/components/BuilderRadioGrid.svelte';
|
||||
import RangeValueControl from '$lib/components/RangeValueControl.svelte';
|
||||
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
|
||||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||||
import {
|
||||
blankRoll, clamp, createDraft, gearBaseStatRange, gearSlots, levelScaledRange, remapRangeValue, scaled, weaponBaseRange,
|
||||
type LoadoutDraft, type Roll
|
||||
} from '$lib/builder/loadout';
|
||||
|
||||
let { data } = $props();
|
||||
let draft = $state<LoadoutDraft>(createDraft());
|
||||
let { data, form } = $props();
|
||||
let draft = $state<LoadoutDraft>(untrack(() => data.initialBuild?.loadout ?? createDraft()));
|
||||
let active = $state('gear:Holster');
|
||||
let saved = $state(false);
|
||||
let message = $state('');
|
||||
let persistedBuildId = $state(untrack(() => data.initialBuild?.id ?? ''));
|
||||
let persistedSlug = $state(untrack(() => data.initialBuild?.slug ?? ''));
|
||||
let persistedVisibility = $state(untrack(() => data.initialBuild?.visibility ?? 'private'));
|
||||
let handledBuildToken = '';
|
||||
const weaponRangeKeys = ['damage','firepower','toughness','engineering'] as const;
|
||||
const coreKeys = ['firepower','toughness','engineering'] as const;
|
||||
const gearAttributes = $derived(data.attributes.filter((item) => item.gear_min !== null).map((item) => ({ ...item, category: `${item.type} · ${item.unit || 'FLAT'}` })));
|
||||
@@ -285,6 +290,7 @@
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (data.initialBuild) return;
|
||||
const stored = localStorage.getItem('shd-build-loadout-draft-v1');
|
||||
if (!stored) return;
|
||||
try {
|
||||
@@ -306,6 +312,18 @@
|
||||
}
|
||||
} catch { localStorage.removeItem('shd-build-loadout-draft-v1'); }
|
||||
});
|
||||
$effect(() => {
|
||||
if (!form?.buildSaved || !form?.buildToken || form.buildToken === handledBuildToken) return;
|
||||
handledBuildToken = form.buildToken;
|
||||
persistedBuildId = form.savedBuild.id;
|
||||
persistedSlug = form.savedBuild.slug;
|
||||
persistedVisibility = form.savedBuild.visibility;
|
||||
message = form.savedBuild.visibility === 'private'
|
||||
? 'Private build saved to your account.'
|
||||
: form.savedBuild.visibility === 'unlisted'
|
||||
? 'Unlisted build saved. Anyone with the link can view it.'
|
||||
: 'Build published to the SHD archive.';
|
||||
});
|
||||
function saveDraft() {
|
||||
localStorage.setItem('shd-build-loadout-draft-v1', JSON.stringify(draft));
|
||||
saved = true; message = 'Draft saved on this device.';
|
||||
@@ -315,8 +333,40 @@
|
||||
|
||||
<svelte:head><title>Build a loadout — SHD.build</title></svelte:head>
|
||||
<main class="app-page builder-page">
|
||||
<div class="builder-command"><div class="page-title"><div><p class="eyebrow"><span></span> LOADOUT WORKBENCH</p><input bind:value={draft.buildName} aria-label="Build name"/></div><div class="actions"><span class:visible={saved}>SAVED LOCALLY</span><button class="primary" onclick={saveDraft}>SAVE DRAFT</button></div></div><div class="builder-status"><span><i></i> LIVE CALCULATION</span><span>12 EQUIPMENT POSITIONS</span><span>{data.gear.length + data.weapons.length + data.chipSets.length + data.protocols.length} CATALOG ITEMS</span></div></div>
|
||||
<div class="builder-command">
|
||||
<div class="page-title">
|
||||
<div><p class="eyebrow"><span></span> LOADOUT WORKBENCH</p><input bind:value={draft.buildName} aria-label="Build name"/></div>
|
||||
<div class="actions">
|
||||
<span class:visible={saved}>SAVED LOCALLY</span>
|
||||
<button class="secondary" onclick={saveDraft}>SAVE LOCALLY</button>
|
||||
{#if data.viewer}
|
||||
<PendingForm action="?/save" class="builder-save-form">
|
||||
{#snippet children(pending)}
|
||||
<input type="hidden" name="id" value={persistedBuildId}/>
|
||||
<input type="hidden" name="loadout" value={JSON.stringify(draft)}/>
|
||||
<button class="primary" type="submit" name="visibility" value={persistedVisibility} aria-label="Save build to account">{pending ? 'SAVING…' : 'SAVE TO ACCOUNT'}</button>
|
||||
{/snippet}
|
||||
</PendingForm>
|
||||
{:else}
|
||||
<a class="primary" href="/sign-in?returnTo=%2Fbuilder">SIGN IN TO SHARE</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if data.viewer}
|
||||
<div class="builder-persistence">
|
||||
<SegmentedChoice legend="ACCOUNT VISIBILITY" name="build-visibility" options={[
|
||||
{ value:'private', label:'PRIVATE' },
|
||||
{ value:'unlisted', label:'UNLISTED' },
|
||||
{ value:'public', label:'PUBLIC' }
|
||||
]} value={persistedVisibility} onchange={(values) => persistedVisibility = values[0]} showIndicator={false} required/>
|
||||
<p>{persistedVisibility === 'private' ? 'Only you can open this build.' : persistedVisibility === 'unlisted' ? 'Anyone with its direct link can open this build.' : 'Listed publicly and accessible by direct link.'}</p>
|
||||
{#if persistedSlug && persistedVisibility !== 'private'}<a class="builder-share-link" href={`/builds/${persistedSlug}`}>VIEW SHARE PAGE →</a>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="builder-status"><span><i></i> LIVE CALCULATION</span><span>12 EQUIPMENT POSITIONS</span><span>{data.gear.length + data.weapons.length + data.chipSets.length + data.protocols.length} CATALOG ITEMS</span></div>
|
||||
</div>
|
||||
{#if message}<p class="success" role="status">{message}</p>{/if}
|
||||
{#if form?.buildError}<p class="error" role="alert">{form.buildError}</p>{/if}
|
||||
{#if !data.gear.length && !data.weapons.length && !data.chipSets.length && !data.protocols.length}<div class="builder-empty-data"><strong>ISAC DATA LINK EMPTY</strong><span>Add active equipment definitions in ISAC to populate this workbench.</span></div>{/if}
|
||||
<div class="loadout-workspace">
|
||||
<section class="loadout-slots">
|
||||
|
||||
Reference in New Issue
Block a user