Files
habitica/website/client/src/components/admin-panel/user-support/achievements.vue
2025-02-11 12:02:50 -06:00

285 lines
7.4 KiB
Vue

<template>
<div class="card mt-2">
<div class="card-header">
<h3
class="mb-0 mt-0"
:class="{'open': expand}"
@click="expand = !expand"
>
Achievements
</h3>
</div>
<div
v-if="expand"
class="card-body"
>
<ul>
<li
v-for="item in achievements"
:key="item.path"
>
<form @submit.prevent="saveItem(item)">
<span
class="enableValueChange"
@click="enableValueChange(item)"
>
<span :class="item.value ? 'achieved' : 'not-achieved'">
{{ item.value }}
</span>
:
{{ itemText(item) }}
</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
v-for="achievementType in nestedAchievementKeys"
:key="achievementType"
>
<div class="accordion-group">
<h4
class="expand-toggle"
:class="{'open': expandItemType[achievementType]}"
@click="expandItemType[achievementType] = !expandItemType[achievementType]"
>
{{ achievementType }}
</h4>
<div v-if="expandItemType[achievementType]">
<ul>
<li
v-for="item in nestedAchievements[achievementType]"
:key="item.path"
>
<form @submit.prevent="saveItem(item)">
<span
class="enableValueChange"
@click="enableValueChange(item)"
>
<span :class="item.value ? 'achieved' : 'not-achieved'">
{{ item.value }}
</span>
:
{{ itemText(item) }}
</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;
}
.achieved {
color: green;
}
.not-achieved {
color: red;
}
</style>
<script>
import content from '@/../../common/script/content';
import i18n from '@/../../common/script/i18n';
import saveHero from '../mixins/saveHero';
function getText (achievementItem) {
if (achievementItem === undefined) {
return '';
}
const { titleKey } = achievementItem;
if (titleKey !== undefined) {
return i18n.t(titleKey, 'en');
}
const { singularTitleKey } = achievementItem;
if (singularTitleKey !== undefined) {
return i18n.t(singularTitleKey, 'en');
}
return achievementItem.key;
}
function collateItemData (self) {
const achievements = [];
const nestedAchievements = {};
const basePath = 'achievements';
const ownedAchievements = self.hero.achievements;
const allAchievements = content.achievements;
for (const key of Object.keys(ownedAchievements)) {
const value = ownedAchievements[key];
if (typeof value === 'object') {
nestedAchievements[key] = [];
for (const nestedKey of Object.keys(value)) {
const valueIsInteger = self.integerTypes.includes(key);
let text = nestedKey;
if (allAchievements[key] && allAchievements[key][nestedKey]) {
text = getText(allAchievements[key][nestedKey]);
}
nestedAchievements[key].push({
key: nestedKey,
text,
achievementType: key,
modified: false,
path: `${basePath}.${key}.${nestedKey}`,
value: value[nestedKey],
valueIsInteger,
});
}
} else {
const valueIsInteger = self.integerTypes.includes(key);
achievements.push({
key,
text: getText(allAchievements[key]),
modified: false,
path: `${basePath}.${key}`,
value: ownedAchievements[key],
valueIsInteger,
});
}
}
for (const key of Object.keys(allAchievements)) {
if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) {
if (ownedAchievements[key] === undefined) {
const valueIsInteger = self.integerTypes.includes(key);
achievements.push({
key,
text: getText(allAchievements[key]),
modified: false,
path: `${basePath}.${key}`,
value: valueIsInteger ? 0 : false,
valueIsInteger,
neverOwned: true,
});
}
}
}
self.achievements = achievements;
self.nestedAchievements = nestedAchievements;
}
function resetData (self) {
collateItemData(self);
self.nestedAchievementKeys.forEach(itemType => { self.expandItemType[itemType] = false; });
}
export default {
mixins: [
saveHero,
],
props: {
resetCounter: {
type: Number,
required: true,
},
hero: {
type: Object,
required: true,
},
},
data () {
return {
expand: false,
expandItemType: {
quests: false,
ultimateGearSets: false,
},
nestedAchievementKeys: ['quests', 'ultimateGearSets'],
integerTypes: ['streak', 'perfect', 'birthday', 'habiticaDays', 'habitSurveys', 'habitBirthdays',
'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests'],
achievements: [],
nestedAchievements: {},
};
},
watch: {
resetCounter () {
resetData(this);
},
},
mounted () {
resetData(this);
},
methods: {
async saveItem (item) {
// prepare the item's new value and path for being saved
this.hero.achievementPath = item.path;
this.hero.achievementVal = item.value;
await this.saveHero({ hero: this.hero, msg: item.path });
item.modified = false;
},
enableValueChange (item) {
// allow form field(s) to be shown:
item.modified = true;
if (!item.valueIsInteger) {
item.value = !item.value;
}
},
itemText (item) {
if (item.key === 'npc') {
return this.$t('npcAchievementName', { key: this.hero.backer && this.hero.backer.npc });
}
if (item.key === 'kickstarter') {
return this.$t('kickstartName', { key: this.hero.backer && this.hero.backer.tier });
}
return item.text || item.key;
},
},
};
</script>