24 lines
6.1 KiB
Vue
24 lines
6.1 KiB
Vue
<script setup lang="ts">
|
||
import {computed,ref} from 'vue';import UiIcon from './UiIcon.vue';import CoinPurse from './CoinPurse.vue';import {inventoryItemSchema,addInventoryItem,changeItemQuantity,type Character,type InventoryItem} from './model';
|
||
const props=defineProps<{character:Character}>(),emit=defineEmits<{browse:[]}>();
|
||
const items=computed(()=>props.character.inventoryItems||[]),categories=computed(()=>['Weapon','Armor','Adventuring gear','Magic item','Other'].filter(category=>items.value.some(i=>i.category===category))),editing=ref(false),editingId=ref<string|null>(null),error=ref('');
|
||
function blank():InventoryItem{return {id:crypto.randomUUID(),name:'',quantity:1,category:'Other',equipped:false,attuned:false,weight:'',cost:'',notes:''}}
|
||
const draft=ref<InventoryItem>(blank());
|
||
function editor(item?:InventoryItem){draft.value=item?structuredClone(JSON.parse(JSON.stringify(item))):blank();editingId.value=item?.id||null;error.value='';editing.value=true}
|
||
function save(){try{const item=inventoryItemSchema.parse(draft.value);if(editingId.value){const index=items.value.findIndex(i=>i.id===editingId.value);if(index<0)throw new Error('This item changed. Close the editor and reload it.');props.character.inventoryItems=items.value.map((old,i)=>i===index?item:old)}else addInventoryItem(props.character,item);editing.value=false}catch(e){error.value=(e as Error).name==='ZodError'?'Check the item name and quantity (0–9999).':(e as Error).message}}
|
||
function remove(item:InventoryItem){props.character.inventoryItems=items.value.filter(i=>i.id!==item.id)}
|
||
</script>
|
||
<template>
|
||
<section class="actions-folio inventory-folio" aria-labelledby="inventory-heading"><div class="folio-heading"><h2 id="inventory-heading"><UiIcon name="gear" />Inventory</h2><div class="folio-heading-tools"><button class="quiet" @click="emit('browse')">Browse SRD</button><button class="quiet folio-add" @click="editor()">+ Custom</button></div></div>
|
||
<CoinPurse :character="character" />
|
||
<div v-if="!items.length" class="folio-empty"><UiIcon name="gear" /><h3>Pack for the adventure.</h3><p>Weapons, useful supplies and treasures from the road.</p><button class="primary" @click="emit('browse')">Browse items</button><button class="quiet" @click="editor()">Add a custom item</button></div>
|
||
<template v-else><p class="folio-spell-hint">{{items.length}} {{items.length===1?'entry':'entries'}} · {{items.reduce((n,i)=>n+i.quantity,0)}} items carried</p>
|
||
<section v-for="category in categories" :key="category" class="spell-chapter"><h3 class="spell-chapter-title">{{category==='Armor'?'Armour':category==='Weapon'?'Weapons':category==='Magic item'?'Magic items':category==='Other'?'Other belongings':'Adventuring gear'}}</h3>
|
||
<article class="folio-action inventory-entry" v-for="item in items.filter(i=>i.category===category)" :key="item.id"><div class="folio-action-main"><UiIcon :name="item.category==='Weapon'?'actions':item.category==='Armor'?'ac':'gear'" class="folio-action-icon" /><div class="folio-action-copy"><h3>{{item.name}}</h3><p class="folio-modifier">{{[item.weight&&item.weight+' each',item.cost&&item.cost+' each',item.equipped&&'Equipped',item.attuned&&'Attuned'].filter(Boolean).join(' · ')||'In your pack'}}</p></div><div class="inventory-quantity"><button class="quiet" @click="changeItemQuantity(item,-1)" :disabled="item.quantity===0" :aria-label="`Use one ${item.name}`">−</button><span :aria-label="`Quantity ${item.quantity}`">{{item.quantity}}</span><button class="quiet" @click="changeItemQuantity(item,1)" :disabled="item.quantity===9999" :aria-label="`Add one ${item.name}`">+</button></div></div>
|
||
<details class="folio-options"><summary>Item notes & options</summary><p v-if="item.notes" class="folio-action-notes">{{item.notes}}</p><p v-if="item.source" class="inventory-source">{{item.source}}</p><div class="inventory-flags"><label><input type="checkbox" v-model="item.equipped">Equipped</label><label><input type="checkbox" v-model="item.attuned">Attuned</label></div><div class="folio-tools"><button class="quiet" @click="editor(item)">Edit item</button><button class="quiet danger-text" @click="remove(item)" :aria-label="`Remove ${item.name}`">Remove</button></div></details></article></section></template>
|
||
<details class="inventory-notebook" :open="!!character.inventory&&!items.length"><summary><UiIcon name="notes" />Inventory notes</summary><p class="hint">Existing notes stay here. Keep currency, packing lists and anything else you want to record.</p><label>Notes<textarea v-model="character.inventory" rows="5" maxlength="12000" placeholder="Coins, packing lists, treasures…"></textarea></label></details>
|
||
<p v-if="items.length" class="folio-spell-hint">Equipped and attuned are manual markers. Armour class and other sheet stats are edited on your character.</p>
|
||
</section>
|
||
<div v-if="editing" class="overlay" @click.self="editing=false"><section class="drawer" role="dialog" aria-modal="true" :aria-label="editingId?'Edit item':'Add item'"><div class="section-title drawer-heading"><h2><UiIcon name="gear" />{{editingId?'Edit item':'Add item'}}</h2><button class="close" @click="editing=false" aria-label="Close item editor">×</button></div><p v-if="error" class="notice danger" role="alert">{{error}}</p><form @submit.prevent="save"><label>Name<input v-model="draft.name" maxlength="120" required></label><div class="form-grid"><label>Quantity<input type="number" v-model.number="draft.quantity" min="0" max="9999" step="1" required></label><label>Category<select v-model="draft.category"><option>Weapon</option><option>Armor</option><option>Adventuring gear</option><option>Magic item</option><option>Other</option></select></label><label>Weight per item<input v-model="draft.weight" maxlength="120" placeholder="1 lb."></label><label>Value per item<input v-model="draft.cost" maxlength="120" placeholder="5 GP"></label></div><div class="inventory-flags"><label><input type="checkbox" v-model="draft.equipped">Equipped</label><label><input type="checkbox" v-model="draft.attuned">Attuned</label></div><label>Item notes<textarea v-model="draft.notes" rows="5" maxlength="12000"></textarea></label><button class="primary wide">{{editingId?'Save item':'Add item'}}</button></form></section></div>
|
||
</template>
|