mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 21:57:22 +01:00
Client/Inventory/Items (#8734)
* client: start working on Inventory/Items * i18n changes and fixes * initial displaying of eggs, food and potions + sorting * add missing files * remove comment * show food, eggs and potions * add label to dropdowns acting as select menus * popovers * move badge to slot and component if necessary, general refactor * fix quantity ordering * some special items, reorganize
This commit is contained in:
@@ -7,10 +7,10 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
|
||||
ul.navbar-nav.mr-auto
|
||||
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
|
||||
a.nav-link(v-once) {{ $t('tasks') }}
|
||||
router-link.nav-item.dropdown(tag="li", :to="{name: 'inventory'}", :class="{'active': $route.path.startsWith('/inventory')}")
|
||||
router-link.nav-item.dropdown(tag="li", :to="{name: 'items'}", :class="{'active': $route.path.startsWith('/inventory')}")
|
||||
a.nav-link(v-once) {{ $t('inventory') }}
|
||||
.dropdown-menu
|
||||
router-link.dropdown-item(:to="{name: 'inventory'}", exact) {{ $t('inventory') }}
|
||||
router-link.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
|
||||
router-link.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
|
||||
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
|
||||
router-link.nav-item(tag="li", :to="{name: 'market'}", exact)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
h3 {{ this.groupBy === 'type' ? 'Type' : $t('class') }}
|
||||
h3 {{ this.groupBy === 'type' ? $t('equipmentType') : $t('class') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="group in itemsGroups",
|
||||
@@ -15,19 +15,21 @@
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[group.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t(group.label) }}
|
||||
span.custom-control-description(v-once) {{ group.label }}
|
||||
|
||||
.col-10.standard-page
|
||||
.clearfix
|
||||
h1.float-left.mb-0.page-header(v-once) {{ $t('equipment') }}
|
||||
.float-right
|
||||
b-dropdown(text="Sort by", right=true)
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="'Sort 1'", right=true)
|
||||
b-dropdown-item(href="#") Option 1
|
||||
b-dropdown-item(href="#") Option 2
|
||||
b-dropdown-item(href="#") Option 3
|
||||
b-dropdown(text="Group by", right=true)
|
||||
b-dropdown-item(@click="groupBy = 'type'", :class="{'dropdown-item-active': groupBy === 'type'}") Type
|
||||
b-dropdown-item(@click="groupBy = 'class'", :class="{'dropdown-item-active': groupBy === 'class'}") {{ $t('class') }}
|
||||
span.dropdown-label {{ $t('groupBy2') }}
|
||||
b-dropdown(:text="$t(groupBy === 'type' ? 'equipmentType' : 'class')", right=true)
|
||||
b-dropdown-item(@click="groupBy = 'type'", :active="groupBy === 'type'") {{ $t('equipmentType') }}
|
||||
b-dropdown-item(@click="groupBy = 'class'", :active="groupBy === 'class'") {{ $t('class') }}
|
||||
|
||||
drawer(
|
||||
:title="$t('equipment')",
|
||||
@@ -52,7 +54,7 @@
|
||||
:placement="'top'"
|
||||
)
|
||||
span(slot="content")
|
||||
.popover-content-title {{ $t(drawerPreference+'PopoverText') }}
|
||||
.popover-content-text {{ $t(drawerPreference+'PopoverText') }}
|
||||
|
||||
toggle-switch.float-right(
|
||||
:label="$t(costume ? 'useCostume' : 'autoEquipBattleGear')",
|
||||
@@ -66,23 +68,25 @@
|
||||
:key="group",
|
||||
:item="flatGear[activeItems[group]]",
|
||||
:itemContentClass="flatGear[activeItems[group]] ? 'shop_' + flatGear[activeItems[group]].key : null",
|
||||
:showPopover="!!flatGear[activeItems[group]] && flatGear[activeItems[group]].key.indexOf('_base_0') === -1",
|
||||
:label="$t(label)",
|
||||
:selected="true",
|
||||
:emptyItem="!flatGear[activeItems[group]] || flatGear[activeItems[group]].key.indexOf('_base_0') !== -1",
|
||||
:label="label",
|
||||
:popoverPosition="'top'",
|
||||
:starVisible="!costume || user.preferences.costume",
|
||||
@click="equip",
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
equipmentAttributesPopover(:item="ctx.item")
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="true",
|
||||
:show="!costume || user.preferences.costume",
|
||||
@click="equip(ctx.item)",
|
||||
)
|
||||
div(
|
||||
v-for="group in itemsGroups",
|
||||
v-if="viewOptions[group.key].selected",
|
||||
:key="group.key",
|
||||
)
|
||||
h2
|
||||
| {{ $t(group.label) }}
|
||||
| {{ group.label }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{items[group.key].length}}
|
||||
|
||||
@@ -92,20 +96,23 @@
|
||||
v-if="viewOptions[group.key].open || index < itemsPerLine",
|
||||
:item="item",
|
||||
:itemContentClass="'shop_' + item.key",
|
||||
:showPopover="item && item.key.indexOf('_base_0') === -1",
|
||||
:emptyItem="!item || item.key.indexOf('_base_0') !== -1",
|
||||
:key="item.key",
|
||||
:selected="activeItems[item.type] === item.key",
|
||||
:starVisible="!costume || user.preferences.costume",
|
||||
@click="equip",
|
||||
)
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="activeItems[ctx.item.type] === ctx.item.key",
|
||||
:show="!costume || user.preferences.costume",
|
||||
@click="equip(ctx.item)",
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
equipmentAttributesPopover(:item="ctx.item")
|
||||
div(v-if="items[group.key].length === 0")
|
||||
p(v-once) {{ $t('noGearItemsOfType', { type: $t(group.label) }) }}
|
||||
p(v-once) {{ $t('noGearItemsOfType', { type: group.label }) }}
|
||||
a.btn.btn-show-more(
|
||||
v-if="items[group.key].length > itemsPerLine",
|
||||
@click="viewOptions[group.key].open = !viewOptions[group.key].open"
|
||||
) {{ viewOptions[group.key].open ? $t('showLessGearItems', { type: $t(group.label) }) : $t('showAllGearItems', { type: $t(group.label), items: items[group.key].length }) }}
|
||||
) {{ viewOptions[group.key].open ? $t('showLessGearItems', { type: group.label }) : $t('showAllGearItems', { type: group.label, items: items[group.key].length }) }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -126,13 +133,18 @@ import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
|
||||
import Item from 'client/components/inventory/item';
|
||||
import EquipmentAttributesPopover from 'client/components/inventory/equipmentAttributesPopover';
|
||||
import EquipmentAttributesPopover from 'client/components/inventory/equipment/attributesPopover';
|
||||
import StarBadge from 'client/components/inventory/starBadge';
|
||||
import Drawer from 'client/components/inventory/drawer';
|
||||
|
||||
import i18n from 'common/script/i18n';
|
||||
|
||||
export default {
|
||||
name: 'Equipment',
|
||||
components: {
|
||||
Item,
|
||||
EquipmentAttributesPopover,
|
||||
StarBadge,
|
||||
Drawer,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
@@ -145,25 +157,25 @@ export default {
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
costume: false,
|
||||
groupBy: 'type', // or 'class' TODO move to router?
|
||||
groupBy: 'type', // or 'class'
|
||||
gearTypesToStrings: Object.freeze({ // TODO use content.itemList?
|
||||
headAccessory: 'headAccessoryCapitalized',
|
||||
head: 'headgearCapitalized',
|
||||
eyewear: 'eyewear',
|
||||
weapon: 'weaponCapitalized',
|
||||
shield: 'offhandCapitalized',
|
||||
armor: 'armorCapitalized',
|
||||
body: 'body',
|
||||
back: 'back',
|
||||
headAccessory: i18n.t('headAccessoryCapitalized'),
|
||||
head: i18n.t('headgearCapitalized'),
|
||||
eyewear: i18n.t('eyewear'),
|
||||
weapon: i18n.t('weaponCapitalized'),
|
||||
shield: i18n.t('offhandCapitalized'),
|
||||
armor: i18n.t('armorCapitalized'),
|
||||
body: i18n.t('body'),
|
||||
back: i18n.t('back'),
|
||||
}),
|
||||
gearClassesToStrings: Object.freeze({
|
||||
warrior: 'warrior', // TODO immediately calculate $(label) instead of all the times
|
||||
wizard: 'mage',
|
||||
rogue: 'rogue',
|
||||
healer: 'healer',
|
||||
special: 'special',
|
||||
mystery: 'mystery',
|
||||
armoire: 'armoireText',
|
||||
warrior: i18n.t('warrior'),
|
||||
wizard: i18n.t('mage'),
|
||||
rogue: i18n.t('rogue'),
|
||||
healer: i18n.t('healer'),
|
||||
special: i18n.t('special'),
|
||||
mystery: i18n.t('mystery'),
|
||||
armoire: i18n.t('armoireText'),
|
||||
}),
|
||||
viewOptions: {},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
secondary-menu.col-12
|
||||
router-link.nav-link(:to="{name: 'inventory'}", exact) {{ $t('inventory') }}
|
||||
router-link.nav-link(:to="{name: 'items'}", exact) {{ $t('items') }}
|
||||
router-link.nav-link(:to="{name: 'equipment'}") {{ $t('equipment') }}
|
||||
router-link.nav-link(:to="{name: 'stable'}") {{ $t('stable') }}
|
||||
.col-12
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
<template lang="pug">
|
||||
div(v-if="emptyItem")
|
||||
.item-wrapper
|
||||
.item.item-empty
|
||||
.item-content
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
b-popover(
|
||||
v-else,
|
||||
:triggers="['hover']",
|
||||
:placement="popoverPosition",
|
||||
v-if="showPopover",
|
||||
@click="click",
|
||||
)
|
||||
span(slot="content")
|
||||
slot(name="popoverContent", :item="item")
|
||||
|
||||
.item-wrapper
|
||||
.item
|
||||
span.badge.badge-pill(
|
||||
:class="{'item-selected-badge': selected === true}",
|
||||
@click="click",
|
||||
v-if="starVisible"
|
||||
) ★
|
||||
slot(name="itemBadge", :item="item")
|
||||
span.item-content(:class="itemContentClass")
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
div(v-else)
|
||||
.item-wrapper
|
||||
.item.item-empty
|
||||
.item-content
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -39,18 +35,12 @@ export default {
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
},
|
||||
starVisible: {
|
||||
type: Boolean,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
showPopover: {
|
||||
emptyItem: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
popoverPosition: {
|
||||
type: String,
|
||||
|
||||
154
website/client/components/inventory/items/index.vue
Normal file
154
website/client/components/inventory/items/index.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.col-2.standard-sidebar
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
h3(v-once) {{ $t('equipmentType') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="group in groups",
|
||||
:key="group.key",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="group.selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t(group.key) }}
|
||||
.col-10.standard-page
|
||||
.clearfix
|
||||
h1.float-left.mb-0.page-header(v-once) {{ $t('items') }}
|
||||
.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(sortBy)", right=true)
|
||||
b-dropdown-item(@click="sortBy = 'quantity'", :active="sortBy === 'quantity'") {{ $t('quantity') }}
|
||||
b-dropdown-item(@click="sortBy = 'AZ'", :active="sortBy === 'AZ'") {{ $t('AZ') }}
|
||||
div(
|
||||
v-for="group in groups",
|
||||
v-if="group.selected",
|
||||
:key="group.key",
|
||||
)
|
||||
h2
|
||||
| {{ $t(group.key) }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{group.quantity}}
|
||||
|
||||
.items
|
||||
item(
|
||||
v-for="({data: item, quantity}, index) in items[group.key]",
|
||||
v-if="group.open || index < itemsPerLine",
|
||||
:item="item",
|
||||
:key="item.key",
|
||||
:itemContentClass="`${group.classPrefix}${item.key}`"
|
||||
:selected="true",
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
h4.popover-content-title {{ ctx.item.text() }}
|
||||
.popover-content-text {{ ctx.item.notes() }}
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
span.badge.badge-pill.badge-item.badge-quantity {{ quantity }}
|
||||
div(v-if="items[group.key].length === 0")
|
||||
p(v-once) {{ $t('noGearItemsOfType', { type: $t(group.key) }) }}
|
||||
a.btn.btn-show-more(
|
||||
v-if="items[group.key].length > itemsPerLine",
|
||||
@click="group.open = !group.open"
|
||||
) {{ group.open ? $t('showLessGearItems', { type: $t(group.key) }) : $t('showAllGearItems', { type: $t(group.key), items: items[group.key].length }) }}
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import each from 'lodash/each';
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import Item from 'client/components/inventory/item';
|
||||
|
||||
const allowedSpecialItems = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
||||
|
||||
const groups = [
|
||||
['eggs', 'Pet_Egg_'],
|
||||
['hatchingPotions', 'Pet_HatchingPotion_'],
|
||||
['food', 'Pet_Food_'],
|
||||
['special', 'inventory_special_', allowedSpecialItems],
|
||||
].map(([group, classPrefix, allowedItems]) => {
|
||||
return {
|
||||
key: group,
|
||||
quantity: 0,
|
||||
selected: true,
|
||||
open: false,
|
||||
classPrefix,
|
||||
allowedItems,
|
||||
};
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'Items',
|
||||
components: {
|
||||
Item,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
itemsPerLine: 9,
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
groups,
|
||||
sortBy: 'quantity', // or 'AZ'
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
searchText: throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText;
|
||||
}, 250),
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
user: 'user.data',
|
||||
}),
|
||||
items () {
|
||||
const searchText = this.searchTextThrottled;
|
||||
const itemsByType = {};
|
||||
|
||||
this.groups.forEach(group => {
|
||||
const groupKey = group.key;
|
||||
let itemsArray = itemsByType[groupKey] = [];
|
||||
const contentItems = this.content[groupKey];
|
||||
|
||||
each(this.user.items[groupKey], (itemQuantity, itemKey) => {
|
||||
if (itemQuantity > 0 && (!group.allowedItems || group.allowedItems.indexOf(itemKey) !== -1)) {
|
||||
const item = contentItems[itemKey];
|
||||
|
||||
const isSearched = !searchText || item.text().toLowerCase().indexOf(searchText) !== -1;
|
||||
if (isSearched) {
|
||||
itemsArray.push({
|
||||
data: item,
|
||||
quantity: itemQuantity,
|
||||
});
|
||||
|
||||
group.quantity += itemQuantity;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemsArray.sort((a, b) => {
|
||||
if (this.sortBy === 'quantity') {
|
||||
return b.quantity - a.quantity;
|
||||
} else { // AZ
|
||||
return a.data.text().localeCompare(b.data.text());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return itemsByType;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
48
website/client/components/inventory/starBadge.vue
Normal file
48
website/client/components/inventory/starBadge.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template lang="pug">
|
||||
span.badge.badge-pill.badge-item.badge-star(
|
||||
:class="{'item-selected-badge': selected === true}",
|
||||
@click="click",
|
||||
v-if="show",
|
||||
) ★
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-star {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
left: -9px;
|
||||
color: $gray-400;
|
||||
background: $white;
|
||||
padding: 4.5px 6px;
|
||||
|
||||
&.item-selected-badge {
|
||||
display: block;
|
||||
background: $teal-50;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover > .badge-star {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click () {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user