dnd-character-deploy/src/InventoryFolio.vue
2026-10-08 01:54:05 +00:00

24 lines
6.1 KiB
Vue
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 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>