mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17: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:
20
website/client/README.md
Normal file
20
website/client/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
|
||||
#Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
|
||||
- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
|
||||
|
||||
- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
|
||||
|
||||
The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html
|
||||
|
||||
The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code
|
||||
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
@@ -4,6 +4,7 @@
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
padding: 4px 8px;
|
||||
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
|
||||
}
|
||||
|
||||
.badge-pill {
|
||||
@@ -12,4 +13,17 @@
|
||||
|
||||
.badge-default {
|
||||
background: $gray-500;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.badge-item {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
}
|
||||
|
||||
.badge-quantity {
|
||||
color: $white;
|
||||
right: -9px;
|
||||
padding: 4.5px 8.5px;
|
||||
background-color: $orange-100;
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active, &:hover, &:focus, &.active, &.dropdown-item-active {
|
||||
&:active, &:hover, &:focus, &.active {
|
||||
background-color: rgba(#d5c8ff, 0.32);
|
||||
color: $purple-200;
|
||||
}
|
||||
@@ -48,4 +48,13 @@
|
||||
|
||||
.dropdown + .dropdown {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.dropdown-label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.43;
|
||||
color: $gray-10;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
@@ -1,36 +1,16 @@
|
||||
// Functions
|
||||
|
||||
// From Bootstrap 4
|
||||
// Replace `$search` with `$replace` in `$string`
|
||||
// @author Hugo Giraudel
|
||||
// @param {String} $string - Initial string
|
||||
// @param {String} $search - Substring to replace
|
||||
// @param {String} $replace ('') - New value
|
||||
// @return {String} - Updated string
|
||||
@function str-replace($string, $search, $replace: "") {
|
||||
$index: str-index($string, $search);
|
||||
|
||||
@if $index {
|
||||
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
|
||||
}
|
||||
|
||||
@return $string;
|
||||
}
|
||||
|
||||
// Variables
|
||||
// Variables and Functions
|
||||
@import './utils';
|
||||
@import './colors';
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: $gray-700;
|
||||
}
|
||||
|
||||
* {
|
||||
transition-duration: .15s;
|
||||
transition-property: border-color, box-shadow, color;
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
// Generic components
|
||||
@import './page';
|
||||
|
||||
// Global styles
|
||||
@import './typography';
|
||||
@import './form';
|
||||
@@ -40,5 +20,3 @@ html, body {
|
||||
@import './popover';
|
||||
@import './item';
|
||||
|
||||
// Generic components
|
||||
@import './page';
|
||||
@@ -46,13 +46,13 @@
|
||||
}
|
||||
|
||||
.item .item-content {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
top: 22px;
|
||||
right: 26px;
|
||||
margin: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
@@ -64,26 +64,4 @@
|
||||
text-align: center;
|
||||
color: $gray-400;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.item > .badge {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -12px;
|
||||
color: $gray-400;
|
||||
background: $white;
|
||||
padding: 4.5px 6px;
|
||||
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
|
||||
|
||||
&.item-selected-badge {
|
||||
display: block;
|
||||
background: $teal-50;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover > .badge {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: $gray-700;
|
||||
}
|
||||
|
||||
.standard-sidebar {
|
||||
background: $gray-600;
|
||||
padding: 24px;
|
||||
|
||||
16
website/client/assets/scss/utils.scss
Normal file
16
website/client/assets/scss/utils.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
// From Bootstrap 4
|
||||
// Replace `$search` with `$replace` in `$string`
|
||||
// @author Hugo Giraudel
|
||||
// @param {String} $string - Initial string
|
||||
// @param {String} $search - Substring to replace
|
||||
// @param {String} $replace ('') - New value
|
||||
// @return {String} - Updated string
|
||||
@function str-replace($string, $search, $replace: "") {
|
||||
$index: str-index($string, $search);
|
||||
|
||||
@if $index {
|
||||
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
|
||||
}
|
||||
|
||||
@return $string;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -11,7 +11,8 @@ import UserTasks from './components/userTasks';
|
||||
|
||||
// Inventory
|
||||
import InventoryContainer from './components/inventory/index';
|
||||
import EquipmentPage from './components/inventory/equipment';
|
||||
import ItemsPage from './components/inventory/items/index';
|
||||
import EquipmentPage from './components/inventory/equipment/index';
|
||||
import StablePage from './components/inventory/stable';
|
||||
|
||||
// Social
|
||||
@@ -39,7 +40,7 @@ export default new VueRouter({
|
||||
path: '/inventory',
|
||||
component: InventoryContainer,
|
||||
children: [
|
||||
{ name: 'inventory', path: '', component: Page },
|
||||
{ name: 'items', path: 'items', component: ItemsPage },
|
||||
{ name: 'equipment', path: 'equipment', component: EquipmentPage },
|
||||
{ name: 'stable', path: 'stable', component: StablePage },
|
||||
],
|
||||
|
||||
@@ -4,5 +4,10 @@
|
||||
"showAllGearItems": "Show All <%= items %> <%= type %> Gear Items",
|
||||
"showLessGearItems": "Show Less <%= type %> Gear Items",
|
||||
"noGearItemsOfType": "You don't own any pieces of <%= type %>.",
|
||||
"costumeDisabled": "You have disabled your costume."
|
||||
"costumeDisabled": "You have disabled your costume.",
|
||||
"items": "Items",
|
||||
"sortBy": "Sort By",
|
||||
"groupBy2": "Group By",
|
||||
"quantity": "Quantity",
|
||||
"AZ": "A-Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user