mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +01:00
* log armoire, quoest response and cron events to history * show user history in admin panel * allow stats to be edited from admin panel * Improve admin panel stats input * improve setting client in history * fix tests * fix lint * fix armoire buying issue * Improve hero saving * Formatting fix * Improve user history logging * allow class to be changed from admin panel * make terminating subscriptions easier * support decimal extraMonths * Fix editing some achievements in admin panel * log if a user invites party to quest * Log more quest events into user history * make userhistory length configurable * fix some numbered achievements * fix extraMonths field * Automatically set up group plan subs with admin panel * show party info nicer in admin panel * improve admin panel sub handling * add missing brace * display when there are unsaved changes * fix setting group plan * fix showing group id * Display group plan info in admin panel * fix setting hourglass promo date * Improve termination handling in admin panel * reload data after certain save events in admin panel * remove console * fix plan.extraMonths not being reset if terminating a sub * add more options when cancelling subs * reload data after group plan change * Add a way to remove users from a party * fix issue with removing user from party * pass party id correctly * correctly call async function * Improve sub display in admin panel * fix line length * fix line * shorter * plaid * fix(lint): vue code style --------- Co-authored-by: Kalista Payne <sabrecat@gmail.com>
307 lines
9.1 KiB
Vue
307 lines
9.1 KiB
Vue
<template>
|
|
<div class="card mt-2">
|
|
<div class="card-header">
|
|
<h3
|
|
class="mb-0 mt-0"
|
|
:class="{'open': expand}"
|
|
@click="expand = !expand"
|
|
>
|
|
Items
|
|
</h3>
|
|
</div>
|
|
<div
|
|
v-if="expand"
|
|
class="card-body"
|
|
>
|
|
<div>
|
|
The sections below display each item's key (bolded if the player has ever owned it),
|
|
followed by the item's English name.
|
|
<ul>
|
|
<li>
|
|
Click on an item's key or value to change it
|
|
(hovering shows an underline to show where you can click).
|
|
</li>
|
|
<li>For Mounts and Gear, clicking toggles between the allowed values.</li>
|
|
<li>For other item types, clicking gives you a form field to enter a new value.</li>
|
|
<li>Click Save when the correct value is displayed.</li>
|
|
<li>
|
|
You must Save for each item individually but you do not need to reload the user
|
|
between each Save.
|
|
</li>
|
|
<li>If you adjust an item and do not click Save for it, the change will be lost.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div
|
|
v-for="itemType in itemTypes"
|
|
:key="itemType"
|
|
>
|
|
<div class="accordion-group">
|
|
<h4
|
|
class="expand-toggle"
|
|
:class="{'open': expandItemType[itemType]}"
|
|
@click="expandItemType[itemType] = !expandItemType[itemType]"
|
|
>
|
|
{{ itemType }}
|
|
</h4>
|
|
|
|
<div v-if="expandItemType[itemType]">
|
|
<p v-if="itemType === 'pets'">
|
|
A value of -1 means they owned the Pet but Released it
|
|
and have not yet rehatched it.
|
|
</p>
|
|
<p v-if="itemType === 'mounts'">
|
|
A value of "null" means they owned the Mount but Released it
|
|
and have not yet retamed it.
|
|
</p>
|
|
<p v-if="itemType === 'special'">
|
|
When there are 0 of these items, we can't tell if
|
|
they had been owned and were all used, or have never been owned.
|
|
</p>
|
|
<p v-if="itemType === 'gear'">
|
|
A value of true means they own the item now and can wear it.
|
|
A value of false means they used to own it but lost it from Death
|
|
(or an old Rebirth).
|
|
</p>
|
|
<ul>
|
|
<li
|
|
v-for="item in collatedItemData[itemType]"
|
|
:key="item.path"
|
|
>
|
|
<form @submit.prevent="saveItem(item)">
|
|
<span
|
|
class="enableValueChange"
|
|
@click="enableValueChange(item)"
|
|
>
|
|
<span :class="item.value ? 'owned' : 'not-owned'">
|
|
{{ item | displayValue }}
|
|
</span>
|
|
:
|
|
<span :class="{ ownedItem: !item.neverOwned }">{{ item.key }} : </span>
|
|
</span>
|
|
<span v-html="item.name"></span>
|
|
|
|
<div
|
|
v-if="item.modified"
|
|
class="form-inline"
|
|
>
|
|
<input
|
|
v-if="item.valueIsInteger"
|
|
v-model="item.value"
|
|
class="form-control valueField"
|
|
type="number"
|
|
>
|
|
<input
|
|
v-if="item.modified"
|
|
type="submit"
|
|
value="Save"
|
|
class="btn btn-primary"
|
|
>
|
|
</div>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
ul li {
|
|
margin-bottom: 0.2em;
|
|
}
|
|
.ownedItem {
|
|
font-weight: bold;
|
|
}
|
|
.enableValueChange:hover {
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
.valueField {
|
|
min-width: 10ch;
|
|
}
|
|
.owned {
|
|
color: green;
|
|
}
|
|
|
|
.not-owned {
|
|
color: red;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import content from '@/../../common/script/content';
|
|
import getItemDescription from '../mixins/getItemDescription';
|
|
import saveHero from '../mixins/saveHero';
|
|
|
|
function collateItemData (self) {
|
|
const collatedItemData = {};
|
|
self.itemTypes.forEach(itemType => {
|
|
// itemTypes are pets, food, gear, etc
|
|
|
|
// Set up some basic data for this itemType:
|
|
let basePath = `items.${itemType}`;
|
|
let ownedItems = self.hero.items[itemType] || {};
|
|
let allItems = content[itemType];
|
|
if (itemType === 'gear') {
|
|
basePath = 'items.gear.owned';
|
|
ownedItems = self.hero.items.gear.owned || {};
|
|
allItems = content.gear.flat;
|
|
} else if (itemType === 'pets' || itemType === 'mounts') {
|
|
// add the non-Standard pets and mounts
|
|
const ucItemType = (itemType === 'pets') ? 'Pets' : 'Mounts';
|
|
self.petMountSubTypes.forEach(subType => {
|
|
allItems = { ...allItems, ...content[subType + ucItemType] };
|
|
});
|
|
}
|
|
|
|
const itemData = []; // all items for this itemType
|
|
|
|
// Collate data for items that the user owns or used to own:
|
|
for (const key of Object.keys(ownedItems)) {
|
|
// Do not sort keys. The order in the items object gives hints about order received.
|
|
if (itemType !== 'special' || self.specialItems.includes(key)) {
|
|
const valueIsInteger = !self.nonIntegerTypes.includes(itemType);
|
|
itemData.push({
|
|
neverOwned: false,
|
|
itemType,
|
|
key,
|
|
modified: false,
|
|
name: self.getItemDescription(itemType, key),
|
|
path: `${basePath}.${key}`,
|
|
value: ownedItems[key],
|
|
valueIsInteger,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Collate data for items that the user never owned:
|
|
for (const key of Object.keys(allItems).sort()) {
|
|
if (
|
|
// ignore items the user owns because we captured them above:
|
|
!(key in ownedItems)
|
|
|
|
// ignore gear items that indicate empty equipped slots (e.g., head_base_0):
|
|
&& !(itemType === 'gear' && content.gear.flat[key].set
|
|
&& content.gear.flat[key].set === 'base-0')
|
|
|
|
// ignore "special" items that aren't Snowballs, Seafoam, etc:
|
|
&& (itemType !== 'special' || self.specialItems.includes(key))
|
|
) {
|
|
const valueIsInteger = !self.nonIntegerTypes.includes(itemType);
|
|
const value = (valueIsInteger) ? 0 : '';
|
|
itemData.push({
|
|
neverOwned: true,
|
|
itemType,
|
|
key,
|
|
modified: false,
|
|
name: self.getItemDescription(itemType, key),
|
|
path: `${basePath}.${key}`,
|
|
value,
|
|
valueIsInteger,
|
|
});
|
|
}
|
|
}
|
|
collatedItemData[itemType] = itemData;
|
|
});
|
|
return collatedItemData;
|
|
}
|
|
|
|
function resetData (self) {
|
|
self.collatedItemData = collateItemData(self);
|
|
self.itemTypes.forEach(itemType => { self.expandItemType[itemType] = false; });
|
|
}
|
|
|
|
export default {
|
|
filters: {
|
|
displayValue (item) {
|
|
if (item.value === '') return 'never owned';
|
|
if (item.value === 0 && item.neverOwned) return '0 (never owned)';
|
|
if (item.value === null) return 'null'; // we need visible text
|
|
return item.value; // true or false or an integer
|
|
},
|
|
},
|
|
mixins: [
|
|
getItemDescription,
|
|
saveHero,
|
|
],
|
|
props: {
|
|
resetCounter: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
hero: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
},
|
|
data () {
|
|
return {
|
|
expand: false,
|
|
expandItemType: {
|
|
eggs: false,
|
|
hatchingPotions: false,
|
|
food: false,
|
|
pets: false,
|
|
mounts: false,
|
|
quests: false,
|
|
gear: false,
|
|
special: false,
|
|
},
|
|
itemTypes: ['eggs', 'hatchingPotions', 'food', 'pets', 'mounts', 'quests', 'gear', 'special'],
|
|
nonIntegerTypes: ['mounts', 'gear'],
|
|
petMountSubTypes: ['premium', 'quest', 'special', 'wacky'], // e.g., 'premiumPets'
|
|
// items.special includes many things but we are interested in these only:
|
|
specialItems: ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'],
|
|
collatedItemData: {},
|
|
};
|
|
},
|
|
watch: {
|
|
resetCounter () {
|
|
resetData(this);
|
|
},
|
|
},
|
|
mounted () {
|
|
resetData(this);
|
|
},
|
|
methods: {
|
|
async saveItem (item) {
|
|
// prepare the item's new value and path for being saved
|
|
const toSave = {
|
|
_id: this.hero._id,
|
|
};
|
|
toSave.itemPath = item.path;
|
|
if (item.value === null) {
|
|
toSave.itemVal = 'null';
|
|
} else if (item.value === false) {
|
|
toSave.itemVal = 'false';
|
|
} else {
|
|
toSave.itemVal = item.value;
|
|
}
|
|
|
|
await this.saveHero({ hero: toSave, msg: item.key });
|
|
item.neverOwned = false;
|
|
item.modified = false;
|
|
},
|
|
enableValueChange (item) {
|
|
// allow form field(s) to be shown:
|
|
item.modified = true;
|
|
|
|
// for non-integer items, toggle through the allowed values:
|
|
if (item.itemType === 'gear' || item.itemType === 'mounts') {
|
|
// Allowed starting values are true, false, and undefined (never owned)
|
|
if (item.value && item.value !== '') {
|
|
item.value = false;
|
|
} else if (typeof item.value === 'boolean') {
|
|
item.value = '';
|
|
} else {
|
|
item.value = true;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|