Inventory: fixes / layout (#11948)

Co-authored-by: Sabe Jones <sabrecat@gmail.com>
This commit is contained in:
negue
2020-10-19 23:54:51 +02:00
committed by GitHub
parent d62930b9da
commit bad3f82dfb
48 changed files with 1612 additions and 431 deletions

View File

@@ -0,0 +1,52 @@
import {
generateUser,
} from '../../../helpers/api-integration/v4';
import { UNEQUIP_EQUIPPED } from '../../../../website/common/script/ops/unequip';
describe('POST /user/unequip', () => {
let user;
beforeEach(async () => {
user = await generateUser({
preferences: {
background: 'violet',
},
items: {
currentMount: 'BearCub-Base',
currentPet: 'BearCub-Base',
gear: {
owned: {
weapon_warrior_0: true,
weapon_warrior_1: true,
weapon_warrior_2: true,
weapon_wizard_1: true,
weapon_wizard_2: true,
shield_base_0: true,
shield_warrior_1: true,
},
equipped: {
weapon: 'weapon_warrior_2',
shield: 'shield_warrior_1',
},
costume: {
weapon: 'weapon_warrior_2',
shield: 'shield_warrior_1',
},
},
},
stats: { gp: 200 },
});
});
// More tests in common code unit tests
context('Gear', () => {
it('should unequip all battle gear items', async () => {
await user.post(`/user/unequip/${UNEQUIP_EQUIPPED}`);
await user.sync();
expect(user.items.gear.equipped.weapon).to.eq('weapon_base_0');
expect(user.items.gear.equipped.shield).to.eq('shield_base_0');
});
});
});

100
test/common/ops/unequip.js Normal file
View File

@@ -0,0 +1,100 @@
/* eslint-disable camelcase */
import {
generateUser,
} from '../../helpers/common.helper';
import {
UNEQUIP_ALL,
UNEQUIP_BACKGROUND,
UNEQUIP_COSTUME,
UNEQUIP_EQUIPPED,
UNEQUIP_PET_MOUNT,
unEquipByType,
} from '../../../website/common/script/ops/unequip';
describe('shared.ops.unequip', () => {
let user;
beforeEach(() => {
user = generateUser({
preferences: {
background: 'violet',
},
items: {
currentMount: 'BearCub-Base',
currentPet: 'BearCub-Base',
gear: {
owned: {
weapon_warrior_0: true,
weapon_warrior_1: true,
weapon_warrior_2: true,
weapon_wizard_1: true,
weapon_wizard_2: true,
shield_base_0: true,
shield_warrior_1: true,
},
equipped: {
weapon: 'weapon_warrior_2',
shield: 'shield_warrior_1',
},
costume: {
weapon: 'weapon_warrior_2',
shield: 'shield_warrior_1',
},
},
},
stats: { gp: 200 },
});
});
context('Gear', () => {
it('should unequip all battle gear items', () => {
unEquipByType(user, { params: { type: UNEQUIP_EQUIPPED } });
expect(user.items.gear.equipped.weapon).to.eq('weapon_base_0');
expect(user.items.gear.equipped.shield).to.eq('shield_base_0');
});
});
context('Costume', () => {
it('should unequip all costume items', () => {
unEquipByType(user, { params: { type: UNEQUIP_COSTUME } });
expect(user.items.gear.costume.weapon).to.eq('weapon_base_0');
expect(user.items.gear.costume.shield).to.eq('shield_base_0');
});
});
context('Pet and Mount', () => {
it('should unequip Pet and Mount', () => {
unEquipByType(user, { params: { type: UNEQUIP_PET_MOUNT } });
expect(user.items.currentMount).to.eq('');
expect(user.items.currentPet).to.eq('');
});
});
context('Background', () => {
it('should unequip Background', () => {
unEquipByType(user, { params: { type: UNEQUIP_BACKGROUND } });
expect(user.preferences.background).to.eq('');
});
});
context('All Items', () => {
it('should unequip all Items', () => {
unEquipByType(user, { params: { type: UNEQUIP_ALL } });
expect(user.items.gear.equipped.weapon).to.eq('weapon_base_0');
expect(user.items.gear.equipped.shield).to.eq('shield_base_0');
expect(user.items.gear.costume.weapon).to.eq('weapon_base_0');
expect(user.items.gear.costume.shield).to.eq('shield_base_0');
expect(user.items.currentMount).to.eq('');
expect(user.items.currentPet).to.eq('');
expect(user.preferences.background).to.eq('');
});
});
});

View File

@@ -50,4 +50,5 @@ function loadStories () {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);

View File

@@ -2,13 +2,15 @@
font-size: 12px;
font-weight: bold;
line-height: 1.33;
color: $gray-200;
padding: 4px 8px;
text-align: center;
color: $gray-100;
padding: 0.25rem 0.5rem;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
}
.badge-pill {
border-radius: 100px;
border-radius: 12px;
}
.badge-round {
@@ -18,7 +20,7 @@
}
.badge-default {
background: $gray-500;
background: $gray-600;
box-shadow: none;
}

View File

@@ -5,7 +5,7 @@
font-weight: bold;
line-height: 1.71;
border: 1px solid transparent;
padding: 0.219rem 1rem;
padding: 0.219rem 0.75rem;
border-radius: 2px;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
color: $white;
@@ -22,7 +22,7 @@
border-color: $purple-400;
}
&:active, &.active:not(.btn-flat), &:disabled, &.disabled {
&:active, &.active:not(.btn-flat) {
box-shadow: none;
}
@@ -30,6 +30,13 @@
cursor: default;
opacity: 0.75;
}
&.with-icon {
height: 2rem; // otherwise would something set the height to 33px
display: flex;
flex-direction: row;
align-items: center;
}
}
.btn-front {
@@ -41,34 +48,49 @@
.btn-primary {
background: $purple-200;
border: 1px solid transparent;
&:hover:not(:disabled):not(.disabled) {
background: #5d3b9c;
border: 1px solid transparent;
}
--icon-color: #{$purple-500};
&:focus {
background: $purple-200;
border-color: $purple-400;
--icon-color: #{$white};
}
&:not(:disabled):not(.disabled) {
&:hover {
background: #5d3b9c;
border: 1px solid transparent;
--icon-color: #{$white};
}
&:active, &.active {
background: $purple-200;
border: 1px solid transparent;
--icon-color: #{$white};
}
&:active:focus, &.active:focus {
box-shadow: none;
border-color: $purple-400;
}
}
&:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active {
background: $purple-200;
border: 1px solid transparent;
}
&:disabled, &.disabled {
background: $purple-200;
background: $gray-700;
border: 1px solid transparent;
cursor: default;
opacity: 0.75;
color: $gray-50;
--icon-color: #{$gray-300};
}
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}
@@ -80,29 +102,27 @@
border: 1px solid transparent;
color: $gray-50;
--icon-color: #{$gray-200};
&:focus, &:active {
color: $gray-50;
background: $white;
border-color: $purple-400;
--icon-color: #{$purple-300};
}
&:not(:disabled):not(.disabled) {
&:active:focus,
&.active:focus {
&:active, &.active {
color: $purple-300;
box-shadow: none;
border-color: $purple-400;
}
--icon-color: #{$purple-300};
&:active,
&.active {
color: $purple-300;
&.dropdown-toggle {
color: $gray-50;
&:focus {
color: $purple-300;
box-shadow: none;
border-color: $purple-400;
}
background: $white;
border: 1px solid transparent;
}
@@ -110,12 +130,10 @@
&:hover {
color: $purple-300;
&.dropdown-toggle {
color: $gray-50;
}
background: $white !important;
border: 1px solid transparent;
--icon-color: #{$purple-300};
}
}
@@ -125,6 +143,14 @@
border: 1px solid transparent;
cursor: default;
opacity: 0.75;
--icon-color: #{$gray-300};
}
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}

View File

@@ -1,9 +1,8 @@
.dropdown > .btn {
padding: 0.25rem 0.75rem;
padding: 0.219rem 0.75rem;
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.43;
}
.dropdown-toggle:hover {
@@ -25,6 +24,8 @@
border-right: 5px solid transparent;
border-left: 5px solid transparent;
vertical-align: 0;
margin-left: 1rem;
margin-bottom: 0.1rem;
}
.dropdown-menu {
@@ -54,8 +55,8 @@
&:active, &:hover, &:focus, &.active {
background-color: rgba($purple-600, 0.32) !important;
color: $purple-200 !important;
background-color: rgba($purple-600, 0.25) !important;
color: $purple-300 !important;
}
&.dropdown-inactive {
@@ -75,10 +76,17 @@
.dropdown-label {
font-size: 14px;
font-weight: bold;
line-height: 1.43;
color: $gray-10;
line-height: 1.71;
margin-right: 20px;
margin-left: 20px;
height: 1.5rem;
font-stretch: normal;
font-style: normal;
text-align: right;
margin-top: calc(0.25rem + 1px); // Padding of the dropdown buttons + button border width
margin-bottom: 0.25rem;
}
.dropdown-icon-item {

View File

@@ -42,11 +42,16 @@ input, textarea, input.form-control, textarea.form-control {
}
&.input-search {
background-repeat: no-repeat;
background-position: center left 16px;
background-size: 16px 16px;
background-image: url(~@/assets/svg/for-css/search.svg);
padding-left: 40px;
height: 2rem;
border-radius: 2px;
border: solid 1px $gray-400;
background-color: $white;
font-size: 14px;
line-height: 1.71;
color: $gray-200;
padding: 0.25rem 1rem 0.25rem 0.75rem;
}
&.input-valid, &.input-invalid {
@@ -170,7 +175,7 @@ input, textarea, input.form-control, textarea.form-control {
}
.form-check {
margin-bottom: 0.5rem;
margin-bottom: 0.875rem;
padding-left: 0px;
}
@@ -182,17 +187,21 @@ $bg-disabled-control: #34303a;
margin-bottom: .5rem;
&-label {
padding-top: 3px;
padding-top: 2px;
padding-left: 3px;
}
.custom-control-label::before {
width: 18px;
height: 18px;
background-size: 75% 75%;
background-color: transparent;
border: 2px solid $gray-200;
transition-property: box-shadow;
font-size: 14px;
line-height: 1.71;
color: $gray-50;
&::before {
width: 18px;
height: 18px;
background-size: 75% 75%;
background-color: transparent;
border: 2px solid $gray-200;
transition-property: box-shadow;
}
}
}

View File

@@ -5,10 +5,6 @@
margin-right: 24px;
}
.items > div:last-of-type {
margin-right: 0px;
}
.item-wrapper {
position: relative;
display: inline-block;
@@ -45,14 +41,13 @@
position: relative;
width: 94px;
height: 92px;
border-radius: 2px;
border-radius: 4px;
background: $white;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
border: 1px solid transparent;
cursor: pointer;
&-empty {
background: $gray-10;
box-shadow: none;
cursor: auto;
}
@@ -85,7 +80,7 @@
margin: 0 auto;
}
.drawer-content .item:hover {
.drawer-content .item.item-empty:hover {
border-color: transparent;
box-shadow: none;
}

View File

@@ -20,5 +20,18 @@ body {
}
.page-header {
color: $purple-200;
color: $purple-300;
height: 2rem;
font-size: 24px;
font-weight: bold;
line-height: 1.33;
}
.sub-header {
height: 1.75rem;
font-size: 20px;
font-weight: bold;
line-height: 1.4;
color: $gray-10;
}

View File

@@ -46,12 +46,16 @@ a, a:not([href]):not([tabindex]) {
}
// Headers
h1, h2, h3, h4, h5, h6 {
h1, h2, h5, h6 {
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
color: $gray-10;
}
h3, h4 {
font-weight: bold;
}
h1 {
font-size: 24px;
line-height: 1.67;
@@ -65,15 +69,15 @@ h2 {
}
h3 {
font-size: 16px;
line-height: 1.5;
color: $gray-50;
margin-bottom: 4px;
font-size: 0.875rem;
line-height: 1.71;
color: $gray-10;
}
h4 {
font-size: 14px;
line-height: 1.43;
font-size: 0.875rem;
line-height: 1.71;
color: $gray-100;
}
.textCondensed {

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#878190;" d="M12,7.9h2c0,4.1-3,7.5-7,8.1h0h0c-4-0.6-7-4-7-8.1V3l7-3v2.2
L2,4.3v3.6c0,2.9,2.1,5.5,5,6C9.9,13.4,12,10.9,12,7.9z M14,2V0h-2v2h-2v2h2v2h2V4h2V2H14z"/>
</svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#878190;" d="M12,7.9h2c0,4.1-3,7.5-7,8.1h0h0c-4-0.6-7-4-7-8.1V3l7-3v2.2
L2,4.3v3.6c0,2.9,2.1,5.5,5,6C9.9,13.4,12,10.9,12,7.9z M10,6h6V4h-6V6z"/>
</svg>

After

Width:  |  Height:  |  Size: 552 B

View File

@@ -4,6 +4,7 @@
v-for="attr in ATTRIBUTES"
:key="attr"
class="popover-content-attr"
:class="`attr-${attr}`"
>
<div class="group-content">
<span
@@ -11,7 +12,7 @@
:class="{'hasValue': hasSumValue(attr) }"
>{{ `${$t(attr)}: ` }}</span>
<span
class="popover-content-attr-cell label value"
class="popover-content-attr-cell label key-value value"
:class="{'green': hasSumValue(attr) }"
>{{ `${stats.sum[attr]}` }}</span>
<span
@@ -53,6 +54,16 @@
width: calc(50% - 1px);
background-color: $gray-50;
.attr-str, .attr-int {
padding-top: 0.25rem;
padding-bottom: 0.5rem;
}
.attr-con, .attr-per {
padding-bottom: 0.75rem;
padding-top: 0.5rem;
}
&:nth-of-type(even) {
margin-left: 1px;
}
@@ -70,12 +81,12 @@
}
.popover-content-attr-cell {
width: 70%;
width: 65%;
text-align: left;
&:nth-of-type(even) {
text-align: right;
width: 30%;
width: 35%;
}
&.key {
@@ -115,7 +126,7 @@
.modal-body {
.group-content {
padding: 8px 17px;
padding: 0.25rem 1rem;
}
.popover-content-attr {
@@ -129,32 +140,43 @@
.popover-content-attr-cell {
&.key {
color: $gray-400;
font-size: 16px;
color: $gray-100;
font-size: 0.875rem;
font-weight: bold;
line-height: 1.25;
line-height: 1.71;
opacity: 0.5;
&.hasValue {
color: $gray-50;
opacity: 1;
}
}
&.label {
color: $gray-400;
font-size: 12px;
font-weight: bold;
color: $gray-100;
font-size: 0.75rem;
line-height: 1.33;
opacity: 0.5;
&.bold {
font-weight: bold;
}
&.key-value {
line-height: 1.71;
}
&.hasValue {
color: $gray-200;
opacity: 1;
}
}
&.label.value {
text-align: right;
&.green {
color: $green-10;
opacity: 1;
}
}
}

View File

@@ -4,15 +4,11 @@
id="equipgear-modal"
:visible="true"
:hide-header="true"
:hide-footer="true"
@change="onChange($event)"
>
<div class="close">
<span
class="svg-icon inline icon-10"
aria-hidden="true"
@click="hideDialog()"
v-html="icons.close"
></span>
<div class="dialog-close">
<close-icon @click="hideDialog()" />
</div>
<div
v-if="item != null"
@@ -23,11 +19,12 @@
:member="user"
:avatar-only="true"
:with-background="true"
:hide-class-badge="true"
:override-avatar-gear="memberOverrideAvatarGear(item)"
:sprites-margin="'0px auto auto -1px'"
:show-visual-buffs="false"
/>
<h4 class="title">
<h4 class="title mt-3">
{{ itemText }}
</h4>
<div
@@ -36,14 +33,14 @@
></div>
<span
v-if="showClassTag"
class="classTag"
class="classTag mt-3"
>
<span
class="svg-icon inline icon-24"
class="svg-icon inline icon-16"
v-html="icons[itemClass]"
></span>
<span
class="className textCondensed"
class="className"
:class="itemClass"
>{{ getClassName(itemClass) }}</span>
</span>
@@ -54,10 +51,17 @@
:item="item"
/>
<button
class="btn btn-primary"
class="btn with-icon mt-4"
:class="{'btn-primary': !isEquipped, 'btn-secondary': isEquipped }"
@click="equipItem()"
>
{{ $t(isEquipped ? 'unequip' : 'equip') }}
<span
class="svg-icon color inline icon-16 mr-2"
v-html="isEquipped ? icons.unEquip : icons.equip"
></span>
<span class="button-label">
{{ $t(isEquipped ? 'unequip' : 'equip') }}
</span>
</button>
</div>
</div>
@@ -69,15 +73,38 @@
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/mixins.scss';
#equipgear-modal {
@include centeredModal();
.modal-content {
border-radius: 8px;
box-shadow: 0 14px 28px 0 #1a181d3d, 0 10px 10px 0 #1a181d47;
}
.modal-body {
padding: 2rem 1.5rem;
}
.dialog-close {
}
.modal-dialog {
width: 330px;
.text {
min-height: 0;
}
}
.text {
font-size: 0.875rem;
line-height: 1.71;
text-align: center;
color: $gray-50;
}
.content {
@@ -89,7 +116,6 @@
}
.inner-content {
margin: 33px auto auto;
width: 282px;
}
@@ -102,10 +128,11 @@
.className {
height: 24px;
font-size: 16px;
line-height: 1.5;
font-size: 0.875rem;
line-height: 1.71;
text-align: left;
margin-left: 8px;
font-weight: bold;
}
.healer {
@@ -124,10 +151,15 @@
color: $wizard-color;
}
.title {
color: $gray-10;
}
.attributesGrid {
background-color: $gray-500;
margin: 10px 0 24px;
margin: 1rem 0 0;
border-radius: 4px;
border: 1px solid #f4f4f4;
}
.avatar {
@@ -139,9 +171,9 @@
}
}
button.btn.btn-primary {
margin-top: 24px;
margin-bottom: 24px;
button.btn {
display: inline-flex;
align-items: center;
}
}
</style>
@@ -154,14 +186,18 @@ import svgWarrior from '@/assets/svg/warrior.svg';
import svgWizard from '@/assets/svg/wizard.svg';
import svgRogue from '@/assets/svg/rogue.svg';
import svgHealer from '@/assets/svg/healer.svg';
import svgEquipIcon from '@/assets/svg/equip.svg';
import svgUnEquipIcon from '@/assets/svg/unequip.svg';
import Avatar from '@/components/avatar';
import attributesGrid from '@/components/inventory/equipment/attributesGrid.vue';
import closeIcon from '@/components/shared/closeIcon';
export default {
components: {
Avatar,
attributesGrid,
closeIcon,
},
props: {
item: {
@@ -185,6 +221,8 @@ export default {
wizard: svgWizard,
rogue: svgRogue,
healer: svgHealer,
equip: svgEquipIcon,
unEquip: svgUnEquipIcon,
}),
};
},

View File

@@ -1,65 +1,57 @@
<template>
<div class="row">
<div class="standard-sidebar d-none d-sm-block">
<div class="form-group">
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
</div>
<div class="form">
<h2 v-once>
{{ $t('filter') }}
</h2>
<h3>{{ groupBy === 'type' ? $t('equipmentType') : $t('class') }}</h3>
<div class="form-group">
<div
v-for="group in itemsGroups"
:key="group.key"
class="form-check"
<filter-sidebar>
<div class="form-group" slot="search">
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
<div class="custom-control custom-checkbox">
<input
:id="groupBy + group.key"
v-model="viewOptions[group.key].selected"
class="custom-control-input"
type="checkbox"
>
<label
v-once
class="custom-control-label"
:for="groupBy + group.key"
>{{ group.label }}</label>
</div>
</div>
</div>
</div>
<div class="form">
<filter-group :title="groupBy === 'type' ? $t('equipmentType') : $t('class')">
<checkbox v-for="group in itemsGroups"
:key="group.key"
:id="groupBy + group.key"
:checked.sync="viewOptions[group.key].selected"
:text="group.label"/>
</filter-group>
</div>
</filter-sidebar>
</div>
<div class="standard-page">
<div class="clearfix">
<h1
v-once
class="float-left mb-4 page-header"
>
{{ $t('equipment') }}
</h1>
<div class="float-right">
<span class="dropdown-label">{{ $t('sortBy') }}</span>
<b-dropdown
:text="$t(selectedSortGearBy)"
right="right"
<div class="mb-4 float-left">
<button
class="page-header btn-flat equipment-type-button textCondensed"
:class="{'active': !costumeMode}"
@click="selectDrawerTab('equipment')"
>
<b-dropdown-item
v-for="sort in sortGearBy"
:key="sort"
:active="selectedSortGearBy === sort"
@click="selectedSortGearBy = sort"
>
{{ $t(sort) }}
</b-dropdown-item>
</b-dropdown>
{{ $t('battleGear') }}
</button>
<button
class="page-header btn-flat equipment-type-button textCondensed"
:class="{'active': costumeMode}"
@click="selectDrawerTab('costume')"
>
{{ $t('costume') }}
</button>
</div>
<div class="float-right top-menu">
<span class="dropdown-label">{{ $t('sortBy') }}</span>
<select-translated-array
:right="true"
:items="sortGearBy"
:value="selectedSortGearBy"
@select="selectedSortGearBy = $event"
class="inline"
:inlineDropdown="false"
/>
<span class="dropdown-label">{{ $t('groupBy2') }}</span>
<b-dropdown
:text="$t(groupBy === 'type' ? 'equipmentType' : 'class')"
@@ -78,31 +70,37 @@
{{ $t('class') }}
</b-dropdown-item>
</b-dropdown>
<span class="divider"></span>
<unequip-dropdown />
</div>
</div>
<drawer
:title="$t('equipment')"
:no-title-bottom-padding="true"
:error-message="(costumeMode && !user.preferences.costume) ? $t('costumeDisabled') : null"
:open-status="openStatus"
@toggled="drawerToggled"
>
<div slot="drawer-title-row" class="title-row-tabs">
<div class="drawer-tab">
<a
class="drawer-tab-text"
:class="{'drawer-tab-text-active': !costumeMode}"
@click.prevent.stop="selectDrawerTab('equipment')"
>{{ $t('battleGear') }}</a>
</div>
<div class="drawer-tab">
<a
class="drawer-tab-text"
:class="{'drawer-tab-text-active': costumeMode}"
@click.prevent.stop="selectDrawerTab('costume')"
>{{ $t('costume') }}</a>
</div>
</div>
<div slot="drawer-header">
<div class="drawer-tab-container">
<div class="drawer-tab text-right">
<a
class="drawer-tab-text"
:class="{'drawer-tab-text-active': !costumeMode}"
@click="selectDrawerTab('equipment')"
>{{ $t('equipment') }}</a>
</div>
<div class="clearfix">
<div class="drawer-tab float-left">
<a
class="drawer-tab-text"
:class="{'drawer-tab-text-active': costumeMode}"
@click="selectDrawerTab('costume')"
>{{ $t('costume') }}</a>
</div>
<toggle-switch
class="float-right align-with-tab"
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"
@@ -115,7 +113,7 @@
</div>
<div
slot="drawer-slider"
class="items items-one-line"
class="equipment items items-one-line"
>
<item
v-for="(label, group) in gearTypesToStrings"
@@ -130,7 +128,7 @@
:popover-position="'top'"
:show-popover="flatGear[activeItems[group]]
&& Boolean(flatGear[activeItems[group]].text)"
@click="equipItem(flatGear[activeItems[group]])"
@click="openEquipDialog(flatGear[activeItems[group]])"
>
<template
slot="popoverContent"
@@ -142,8 +140,8 @@
slot="itemBadge"
slot-scope="context"
>
<starBadge
:selected="true"
<equip-badge
:equipped="true"
:show="!costumeMode || user.preferences.costume"
@click="equipItem(context.item)"
/>
@@ -187,8 +185,8 @@
slot="itemBadge"
slot-scope="context"
>
<starBadge
:selected="activeItems[context.item.type] === context.item.key"
<equip-badge
:equipped="activeItems[context.item.type] === context.item.key"
:show="!costumeMode || user.preferences.costume"
@click="equipItem(context.item)"
/>
@@ -215,17 +213,76 @@
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
.pointer {
cursor: pointer;
}
.align-with-tab {
margin-top: 3px;
margin-right: 3px;
}
.drawer-tab-text {
display: inline-block;
}
.equipment.items .item-empty {
background: $gray-10 !important;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.page-header.btn-flat {
background: transparent;
}
.title-row-tabs {
display: flex;
justify-content: center;
.drawer-tab {
background: transparent;
}
}
.equipment-type-button {
height: 2rem;
font-size: 24px;
font-weight: bold;
font-stretch: condensed;
line-height: 1.33;
letter-spacing: normal;
color: $gray-10;
margin-right: 1.125rem;
padding-left: 0;
padding-right: 0;
padding-bottom: 2.5rem;
&.active, &:hover {
color: $purple-300;
box-shadow: 0px -0.25rem 0px $purple-300 inset;
outline: none;
}
}
.divider {
width: 0.063rem;
height: 2rem;
background-color: $gray-500;
margin-left: 1rem;
margin-right: 1rem;
}
.top-menu {
display: flex;
align-items: center;
}
</style>
<script>
@@ -242,13 +299,19 @@ import toggleSwitch from '@/components/ui/toggleSwitch';
import Item from '@/components/inventory/item';
import ItemRows from '@/components/ui/itemRows';
import EquipmentAttributesPopover from '@/components/inventory/equipment/attributesPopover';
import StarBadge from '@/components/ui/starBadge';
import Drawer from '@/components/ui/drawer';
import i18n from '@/../../common/script/i18n';
import EquipGearModal from './equipGearModal';
import FilterGroup from '@/components/ui/filterGroup';
import FilterSidebar from '@/components/ui/filterSidebar';
import Checkbox from '@/components/ui/checkbox';
import UnequipDropdown from '@/components/inventory/equipment/unequipDropdown';
import EquipBadge from '@/components/ui/equipBadge';
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
const sortGearTypes = ['sortByName', 'sortByCon', 'sortByPer', 'sortByStr', 'sortByInt'];
const sortGearTypeMap = {
@@ -262,10 +325,15 @@ const sortGearTypeMap = {
export default {
name: 'Equipment',
components: {
SelectTranslatedArray,
EquipBadge,
UnequipDropdown,
Checkbox,
FilterSidebar,
FilterGroup,
Item,
ItemRows,
EquipmentAttributesPopover,
StarBadge,
Drawer,
toggleSwitch,
EquipGearModal,
@@ -452,6 +520,7 @@ export default {
this.costumeMode = false;
}
setLocalSetting(CONSTANTS.keyConstants.CURRENT_EQUIPMENT_DRAWER_TAB, tabNameValue);
this.$store.state.equipmentDrawerOpen = true;
},
openEquipDialog (item) {
this.gearToEquip = item;

View File

@@ -0,0 +1,123 @@
<template>
<b-dropdown
right="right"
toggle-class="with-icon"
>
<template v-slot:button-content>
<span class="svg-icon inline color"
v-html="icons.unequipIcon">
</span>
<span class="button-label" v-once>{{ $t('unequip') }}</span>
</template>
<b-dropdown-item
@click="unequipBattleGear()"
>
{{ $t('battleGear') }}
</b-dropdown-item>
<b-dropdown-item
@click="unequipCostume()"
>
{{ $t('costume') }}
</b-dropdown-item>
<b-dropdown-item
@click="unequipPetMount()"
>
{{ $t('petAndMount') }}
</b-dropdown-item>
<b-dropdown-item
@click="unequipBackground()"
>
{{ $t('background') }}
</b-dropdown-item>
<b-dropdown-item
@click="unequipAllItems()"
>
{{ $t('allItems') }}
</b-dropdown-item>
</b-dropdown>
</template>
<script>
import {
UNEQUIP_ALL,
UNEQUIP_COSTUME,
UNEQUIP_EQUIPPED,
UNEQUIP_BACKGROUND,
UNEQUIP_PET_MOUNT,
} from '../../../../../common/script/ops/unequip';
import unequipIcon from '@/assets/svg/unequip.svg';
export default {
name: 'unequipDropdown',
data () {
return {
icons: Object.freeze({
unequipIcon,
}),
};
},
methods: {
unequipPetMountBackground () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_PET_MOUNT,
});
},
unequipBattleGear () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_EQUIPPED,
});
},
unequipCostume () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_COSTUME,
});
},
unequipPetMount () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_PET_MOUNT,
});
},
unequipBackground () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_BACKGROUND,
});
},
unequipAllItems () {
this.$store.dispatch('user:unequip', {
type: UNEQUIP_ALL,
});
},
},
};
</script>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
::v-deep {
.btn {
display: flex;
align-items: center;
&::after {
margin-bottom: 0;
}
}
}
.svg-icon {
width: 1rem;
height: 1rem;
margin-right: 0.5rem;
}
.button-label {
width: 3.25rem;
height: 1.5rem;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
}
</style>

View File

@@ -5,71 +5,45 @@
@mouseMoved="mouseMoved($event)"
>
<div class="standard-sidebar d-none d-sm-block">
<div class="form-group">
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
</div>
<div class="form">
<h2 v-once>
{{ $t('filter') }}
</h2>
<h3 v-once>
{{ $t('equipmentType') }}
</h3>
<div class="form-group">
<div
v-for="group in groups"
:key="group.key"
class="form-check"
<filter-sidebar>
<div class="form-group" slot="search">
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
<div class="custom-control custom-checkbox">
<input
:id="group.key"
v-model="group.selected"
class="custom-control-input"
type="checkbox"
>
<label
v-once
class="custom-control-label"
:for="group.key"
>{{ $t(group.key) }}</label>
</div>
</div>
</div>
</div>
<div class="form">
<filter-group :title="$t('equipmentType')">
<checkbox v-for="group in groups"
:key="group.key"
:id="group.key"
:checked.sync="group.selected"
:text="$t(group.key)"/>
</filter-group>
</div>
</filter-sidebar>
</div>
<div class="standard-page">
<div class="clearfix">
<h1
v-once
class="float-left mb-4 page-header"
class="float-left mb-3 page-header"
>
{{ $t('items') }}
</h1>
<div class="float-right">
<span class="dropdown-label">{{ $t('sortBy') }}</span>
<b-dropdown
:text="$t(sortBy)"
right="right"
>
<b-dropdown-item
:active="sortBy === 'quantity'"
@click="sortBy = 'quantity'"
>
{{ $t('quantity') }}
</b-dropdown-item>
<b-dropdown-item
:active="sortBy === 'AZ'"
@click="sortBy = 'AZ'"
>
{{ $t('AZ') }}
</b-dropdown-item>
</b-dropdown>
<select-translated-array
:right="true"
:items="['quantity', 'AZ']"
:value="sortBy"
@select="sortBy = $event"
class="inline"
:inlineDropdown="false"
/>
</div>
</div>
@@ -80,7 +54,7 @@
:key="group.key"
>
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
<h2 class="d-flex align-items-center mb-3">
<h2 class="d-flex align-items-center mb-3 sub-header">
{{ $t(group.key) }}
<span
v-if="group.key != 'special'"
@@ -320,6 +294,8 @@
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.eggInfo, .hatchingPotionInfo {
position: absolute;
left: -500px;
@@ -347,6 +323,7 @@
text-align: center;
}
}
</style>
<script>
@@ -356,6 +333,7 @@ import moment from 'moment';
import Item from '@/components/inventory/item';
import ItemRows from '@/components/ui/itemRows';
import CountBadge from '@/components/ui/countBadge';
import FilterSidebar from '@/components/ui/filterSidebar';
import cardsModal from './cards-modal';
@@ -369,6 +347,9 @@ import { createAnimal } from '@/libs/createAnimal';
import notifications from '@/mixins/notifications';
import DragDropDirective from '@/directives/dragdrop.directive';
import MouseMoveDirective from '@/directives/mouseposition.directive';
import FilterGroup from '@/components/ui/filterGroup';
import Checkbox from '@/components/ui/checkbox';
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
const allowedSpecialItems = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
@@ -391,6 +372,9 @@ let lastMouseMoveEvent = {};
export default {
name: 'Items',
components: {
SelectTranslatedArray,
Checkbox,
FilterGroup,
Item,
ItemRows,
HatchedPetDialog,
@@ -398,6 +382,7 @@ export default {
startQuestModal,
cardsModal,
QuestInfo,
FilterSidebar,
},
directives: {
drag: DragDropDirective,

View File

@@ -47,6 +47,7 @@
<style lang="scss">
@import '~@/assets/scss/mixins.scss';
@import '~@/assets/scss/colors.scss';
#hatching-modal {
@include centeredModal();
@@ -66,7 +67,7 @@
font-weight: bold;
line-height: 1.2;
text-align: center;
color: #4e4a57;
color: $gray-50;
}
.text {
@@ -75,7 +76,7 @@
font-size: 14px;
line-height: 1.43;
text-align: center;
color: #686274;
color: $gray-100;
}
span.svg-icon.icon-10 {
@@ -87,6 +88,27 @@
.modal-footer {
justify-content: center;
}
.potionEggGroup {
margin: 0 auto;
}
.potionEggBackground {
display: inline-flex;
align-items: center;
width: 112px;
height: 112px;
border-radius: 4px;
background-color: $gray-700;
&:first-child {
margin-right: 24px;
}
div {
margin: 0 auto;
}
}
}
</style>

View File

@@ -5,92 +5,92 @@
@mouseMoved="mouseMoved($event)"
>
<div class="standard-sidebar d-none d-sm-block">
<div>
<filter-sidebar>
<div slot="header">
<div
id="npmMattStable"
class="npc_matt"
></div>
<b-popover
triggers="hover"
placement="right"
target="npmMattStable"
>
<h4
v-once
class="popover-content-title"
>
{{ $t('mattBoch') }}
</h4>
<div
v-once
class="popover-content-text"
>
{{ $t('mattBochText1') }}
</div>
</b-popover>
</div>
<div
id="npmMattStable"
class="npc_matt"
></div>
<b-popover
triggers="hover"
placement="right"
target="npmMattStable"
slot="search"
class="form-group"
>
<h4
v-once
class="popover-content-title"
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
{{ $t('mattBoch') }}
</h4>
<div
v-once
class="popover-content-text"
>
{{ $t('mattBochText1') }}
</div>
</b-popover>
</div>
<div class="form-group">
<input
v-model="searchText"
class="form-control input-search"
type="text"
:placeholder="$t('search')"
>
</div>
<div class="form">
<h2 v-once>
{{ $t('filter') }}
</h2>
<h3 v-once>
{{ $t('pets') }}
</h3>
<div class="form-group">
<div
v-for="petGroup in petGroups"
:key="petGroup.key"
class="form-check"
>
<div class="custom-control custom-checkbox">
<input
:id="petGroup.key"
v-model="viewOptions[petGroup.key].selected"
class="custom-control-input"
type="checkbox"
:disabled="viewOptions[petGroup.key].animalCount == 0"
>
<label
v-once
class="custom-control-label"
:for="petGroup.key"
>{{ petGroup.label }}</label>
</div>
<filter-group :title="$t('pets')">
<div class="form-group">
<div
v-for="petGroup in petGroups"
:key="petGroup.key"
class="form-check"
>
<div class="custom-control custom-checkbox">
<input
:id="petGroup.key"
v-model="viewOptions[petGroup.key].selected"
class="custom-control-input"
type="checkbox"
:disabled="viewOptions[petGroup.key].animalCount == 0"
>
<label
v-once
class="custom-control-label"
:for="petGroup.key"
>{{ petGroup.label }}</label>
</div>
</div>
</div>
</div>
<h3 v-once>
{{ $t('mounts') }}
</h3>
<div class="form-group">
<div
v-for="mountGroup in mountGroups"
:key="mountGroup.key"
class="form-check"
>
<div class="custom-control custom-checkbox">
<input
:id="mountGroup.key"
v-model="viewOptions[mountGroup.key].selected"
class="custom-control-input"
type="checkbox"
:disabled="viewOptions[mountGroup.key].animalCount == 0"
>
<label
v-once
class="custom-control-label"
:for="mountGroup.key"
>{{ mountGroup.label }}</label>
</filter-group>
<filter-group :title="$t('mounts')">
<div class="form-group">
<div
v-for="mountGroup in mountGroups"
:key="mountGroup.key"
class="form-check"
>
<div class="custom-control custom-checkbox">
<input
:id="mountGroup.key"
v-model="viewOptions[mountGroup.key].selected"
class="custom-control-input"
type="checkbox"
:disabled="viewOptions[mountGroup.key].animalCount == 0"
>
<label
v-once
class="custom-control-label"
:for="mountGroup.key"
>{{ mountGroup.label }}</label>
</div>
</div>
</div>
</div>
</filter-group>
<div class="form-group clearfix">
<h3 class="float-left">
{{ $t('hideMissing') }}
@@ -101,7 +101,7 @@
@change="updateHideMissing"
/>
</div>
</div>
</filter-sidebar>
</div>
<div class="standard-page">
<div class="clearfix">
@@ -113,19 +113,14 @@
</h1>
<div class="float-right">
<span class="dropdown-label">{{ $t('sortBy') }}</span>
<b-dropdown
:text="$t(selectedSortBy)"
right="right"
>
<b-dropdown-item
v-for="sort in sortByItems"
:key="sort"
:active="selectedSortBy === sort"
@click="selectedSortBy = sort"
>
{{ $t(sort) }}
</b-dropdown-item>
</b-dropdown>
<select-translated-array
:right="true"
:items="sortByItems"
:value="selectedSortBy"
@select="selectedSortBy = $event"
class="inline"
:inlineDropdown="false"
/>
</div>
</div>
<h2 class="mb-3">
@@ -174,8 +169,8 @@
slot="itemBadge"
slot-scope="context"
>
<starBadge
:selected="context.item.key === currentPet"
<equip-badge
:equipped="context.item.key === currentPet"
:show="isOwned('pet', context.item)"
@click="selectPet(context.item)"
/>
@@ -183,13 +178,12 @@
</petItem>
</div>
</div>
<div
<show-more-button
v-if="petGroup.key !== 'specialPets' && !(petGroup.key === 'wackyPets' && selectedSortBy !== 'sortByColor')"
class="btn btn-flat btn-show-more"
:show-all="$_openedItemRows_isToggled(petGroup.key)"
class="show-more-button"
@click="setShowMore(petGroup.key)"
>
{{ $_openedItemRows_isToggled(petGroup.key) ? $t('showLess') : $t('showMore') }}
</div>
/>
</div>
<h2>
{{ $t('mounts') }}
@@ -234,8 +228,8 @@
<template
slot="itemBadge"
>
<starBadge
:selected="item.key === currentMount"
<equip-badge
:equipped="item.key === currentMount"
:show="isOwned('mount', item)"
@click="selectMount(item)"
/>
@@ -243,13 +237,11 @@
</mountItem>
</div>
</div>
<div
<show-more-button
v-if="mountGroup.key !== 'specialMounts'"
class="btn btn-flat btn-show-more"
:show-all="$_openedItemRows_isToggled(mountGroup.key)"
@click="setShowMore(mountGroup.key)"
>
{{ $_openedItemRows_isToggled(mountGroup.key) ? $t('showLess') : $t('showMore') }}
</div>
/>
</div>
<inventoryDrawer>
<template
@@ -332,50 +324,17 @@
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/mixins.scss';
.standard-page .clearfix .float-right {
margin-right: 24px;
}
.inventory-item-container {
padding: 20px;
border: 1px solid;
display: inline-block;
}
.hatchablePopover {
width: 180px
}
.potionEggGroup {
margin: 0 auto;
}
.potionEggBackground {
display: inline-flex;
align-items: center;
width: 112px;
height: 112px;
border-radius: 4px;
background-color: #f9f9f9;
&:first-child {
margin-right: 24px;
}
& div {
margin: 0 auto;
}
}
.GreyedOut {
opacity: 0.3;
}
.item.item-empty {
width: 94px;
height: 92px;
border-radius: 2px;
background-color: #edecee;
}
@@ -389,6 +348,10 @@
padding-right:0;
}
.standard-page .clearfix .float-right {
margin-right: 24px;
}
.svg-icon.inline.icon-16 {
vertical-align: bottom;
}
@@ -439,8 +402,7 @@
width: 180px;
.potionEggGroup {
margin: 0 auto;
margin-top: 10px;
margin: 10px auto 0;
}
.potionEggBackground {
@@ -456,7 +418,7 @@
margin-right: 24px;
}
& div {
div {
margin: 0 auto;
}
}
@@ -480,7 +442,6 @@ import MountRaisedModal from './mountRaisedModal';
import WelcomeModal from './welcomeModal';
import HatchingModal from './hatchingModal';
import toggleSwitch from '@/components/ui/toggleSwitch';
import StarBadge from '@/components/ui/starBadge';
import InventoryDrawer from '@/components/shared/inventoryDrawer';
import ResizeDirective from '@/directives/resize.directive';
@@ -497,6 +458,11 @@ import petMixin from '@/mixins/petMixin';
import { CONSTANTS, setLocalSetting, getLocalSetting } from '@/libs/userlocalManager';
import { isOwned } from '../../../libs/createAnimal';
import FilterSidebar from '@/components/ui/filterSidebar';
import FilterGroup from '@/components/ui/filterGroup';
import ShowMoreButton from '@/components/ui/showMoreButton';
import EquipBadge from '@/components/ui/equipBadge';
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
// TODO Normalize special pets and mounts
// import Store from '@/store';
@@ -507,11 +473,15 @@ let lastMouseMoveEvent = {};
export default {
components: {
SelectTranslatedArray,
EquipBadge,
ShowMoreButton,
FilterGroup,
FilterSidebar,
PetItem,
FoodItem,
MountItem,
toggleSwitch,
StarBadge,
HatchedPetDialog,
MountRaisedModal,
WelcomeModal,
@@ -832,9 +802,6 @@ export default {
return groupBy(mounts, groupKey);
},
hasDrawerTabItems (index) {
return this.drawerTabs && this.drawerTabs[index].items.length !== 0;
},
// Actions
updateHideMissing (newVal) {
this.hideMissing = newVal;

View File

@@ -2,14 +2,14 @@
<div class="payments-column mx-auto mt-auto">
<button
v-if="stripeAvailable"
class="btn btn-primary payment-button payment-item"
class="btn btn-primary payment-button payment-item with-icon"
:class="{disabled}"
:disabled="disabled"
@click="stripeFn()"
>
<div
v-once
class="svg-icon credit-card-icon"
class="svg-icon color credit-card-icon"
v-html="icons.creditCardIcon"
></div>
{{ $t('card') }}

View File

@@ -11,18 +11,22 @@
background: $gray-600;
box-shadow: 0 1px 2px 0 rgba($black, 0.2);
z-index: 9;
height: 3rem;
}
.nav-link {
font-size: 16px;
line-height: 1.5;
padding: 16px 24px;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
text-align: center;
padding: 0.75rem;
color: $gray-50;
&.active {
color: $purple-200;
box-shadow: 0px -4px 0px $purple-300 inset;
color: $purple-300;
box-shadow: 0px -0.25rem 0px $purple-300 inset;
}
&:hover {

View File

@@ -0,0 +1,53 @@
<template>
<button title="close dialog"
@click="$emit('click', $event)">
<div v-once
class="svg-icon"
v-html="icons.close"
></div>
</button>
</template>
<script>
import svgClose from '@/assets/svg/close.svg';
export default {
name: 'closeIcon',
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
};
</script>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
button {
position: absolute;
top: 1rem;
right: 1rem;
cursor: pointer;
opacity: 1;
border: 0;
outline: 0;
padding: 0.125rem;
background: transparent;
width: 1rem;
height: 1rem;
::v-deep svg path {
stroke: $gray-200;
}
&:hover {
::v-deep svg path {
stroke: $gray-100;
}
}
}
</style>

View File

@@ -1,12 +1,23 @@
<template>
<drawer
class="inventoryDrawer"
:title="$t('quickInventory')"
:no-title-bottom-padding="true"
:error-message="inventoryDrawerErrorMessage(selectedDrawerItemType)"
ref="drawer"
>
<div slot="drawer-title-row" class="title-row-tabs">
<div class="drawer-tab" v-for="(tab, index) of filteredTabs"
:key="tab.key">
<a
class="drawer-tab-text"
:class="{'drawer-tab-text-active': filteredTabs[selectedDrawerTab].key === tab.key}"
@click.prevent.stop="tabSelected(index); $refs.drawer.open();"
>{{ tab.label }}</a>
</div>
</div>
<div slot="drawer-header">
<drawer-header-tabs
:tabs="filteredTabs"
:tabs="[]"
@changedPosition="tabSelected($event)"
>
<div slot="right-item">
@@ -15,7 +26,7 @@
id="petLikeToEatMarket"
class="drawer-help-text"
>
{{ $t('petLikeToEat') + ' ' }}
<span>{{ $t('petLikeToEat') + ' ' }}</span>
<span
class="svg-icon inline icon-16"
v-html="icons.information"
@@ -178,5 +189,28 @@ export default {
.drawer-slider {
height: 126px;
}
.drawer-tab-text {
display: inline-block;
}
.drawer-help-text {
display: flex;
margin-top: 0.65rem;
.svg-icon {
position: inherit;
top: 0;
}
}
.title-row-tabs {
display: flex;
justify-content: center;
.drawer-tab {
background: transparent;
}
}
}
</style>

View File

@@ -7,6 +7,7 @@
:class="{disabled: disabled}"
:disabled="disabled"
:value="selected"
:hide-icon="true"
@select="$emit('select', $event.value)"
>
<template v-slot:item="{ item, button }">

View File

@@ -6,6 +6,9 @@
class="array-select"
:class="{disabled: disabled}"
:disabled="disabled"
:right="right"
:hide-icon="false"
:inline-dropdown="inlineDropdown"
@select="selectItem($event)"
>
<template v-slot:item="{ item }">
@@ -60,6 +63,13 @@ export default {
type: Boolean,
},
value: [String, Number, Object],
right: {
type: Boolean,
},
inlineDropdown: {
type: Boolean,
default: true,
},
},
data () {
return {

View File

@@ -251,10 +251,6 @@
padding-top: 16px;
}
.input-search, .search-button {
height: 40px;
}
.tasks-navigation {
margin-bottom: 20px;
}

View File

@@ -0,0 +1,129 @@
/* eslint-disable import/no-extraneous-dependencies */
import { storiesOf } from '@storybook/vue';
import { withKnobs } from '@storybook/addon-knobs';
import positiveIcon from '@/assets/svg/positive.svg';
const stories = storiesOf('Buttons', module);
stories.addDecorator(withKnobs);
stories
.add('all', () => ({
components: { },
data () {
return {
icon: positiveIcon,
};
},
template: `
<div style="position: absolute; margin: 20px; display: flex; flex-direction: row;">
<div class="mr-3">
<h3>Button</h3>
<button class="btn btn-primary">Button Primary</button>
<br/><br/>
<button class="btn btn-primary" disabled>Button Primary Disabled</button>
<br/><br/>
<button class="btn btn-secondary">Button Secondary</button>
<br/><br/>
<button class="btn btn-secondary" disabled>Button Secondary Disabled</button>
</div>
<div class="">
<h3>Button with Icon</h3>
<button class="btn btn-primary with-icon">
<span class="svg-icon color inline icon-12 mr-2"
v-html="icon"
>
</span>
<span class="button-label">
Button Primary
</span>
</button>
<br/>
<button class="btn btn-primary with-icon" disabled>
<span class="svg-icon color inline icon-12 mr-2"
v-html="icon"
>
</span>
<span class="button-label">
Button Primary Disabled
</span>
</button>
<br/>
<button class="btn btn-secondary with-icon">
<span class="svg-icon color inline icon-12 mr-2"
v-html="icon"
>
</span>
<span class="button-label">
Button Secondary
</span>
</button>
<br/>
<button class="btn btn-secondary with-icon" disabled>
<span class="svg-icon color inline icon-12 mr-2"
v-html="icon"
>
</span>
<span class="button-label">
Button Secondary Disabled
</span>
</button>
</div>
</div>
`,
}))
.add('dropdowns', () => ({
components: { },
data () {
return {
items: ['one', 'two', 'three'],
};
},
template: `
<div style="position: absolute; margin: 20px; display: flex; flex-direction: row;">
<div class="mr-3">
<h3>Dropdowns</h3>
<b-dropdown
text="Dropdown Primary"
right="right"
>
<b-dropdown-item
v-for="item in items"
:key="item"
>
{{ item }}
</b-dropdown-item>
</b-dropdown>
<br/><br/>
<b-dropdown
text="Dropdown Primary Disabled"
right="right"
disabled
>
</b-dropdown>
</div>
<div class="">
<h3>Button</h3>
<button class="btn btn-secondary">
<span class="button-label">
Button Primary
</span>
</button>
<br/>
<br/>
<button class="btn btn-secondary" disabled>
<span class="button-label">
Button Primary Disabled
</span>
</button>
</div>
</div>
`,
}));

View File

@@ -26,6 +26,13 @@
label:not(:disabled):not(.disabled) {
cursor: pointer;
}
.custom-control.custom-checkbox {
display: flex;
flex-direction: row;
justify-items: center;
}
</style>
<script>

View File

@@ -2,9 +2,14 @@
<div class="drawer-container">
<div
class="drawer-title"
:class="{'no-padding': noTitleBottomPadding}"
@click="toggle()"
>
{{ title }}
<div class="title-row">
<slot name="drawer-title-row">
<div class="text-only">{{ title }}</div>
</slot>
</div>
<div
class="drawer-toggle-icon svg-icon icon-10"
:class="{ closed: !isOpen }"
@@ -59,7 +64,7 @@
.drawer-toggle-icon {
position: absolute;
right: 16px;
top: 16px;
bottom: 0;
&.closed {
top: 10px;
@@ -67,6 +72,7 @@
}
.drawer-title {
height: 2rem;
position: relative;
background-color: $gray-10;
box-shadow: 0 1px 2px 0 rgba($black, 0.2);
@@ -74,9 +80,30 @@
border-top-right-radius: 8px;
border-top-left-radius: 8px;
text-align: center;
line-height: 1.67;
line-height: 1.33;
color: $white;
padding: 6px 0;
font-size: 12px;
font-weight: bold;
display: flex;
&.no-padding {
padding-bottom: 0;
}
}
.text-only {
padding-top: 0.5rem;
}
.title-row {
flex: 1;
display: flex;
justify-content: center;
& > div {
flex: 1;
}
}
.drawer-content {
@@ -103,13 +130,14 @@
&-text {
font-size: 12px;
font-weight: bold;
line-height: 1.67;
text-align: center;
color: $white;
color: $gray-400;
text-decoration: none !important;
border-bottom: 2px solid transparent;
padding: 0px 8px 8px 8px;
padding: 0.5rem;
&-active {
&-active, &:hover {
color: $white !important;
border-color: $purple-400;
}
}
@@ -167,7 +195,6 @@ export default {
props: {
title: {
type: String,
required: true,
},
errorMessage: {
type: String,
@@ -175,10 +202,13 @@ export default {
openStatus: {
type: Number,
},
noTitleBottomPadding: {
type: Boolean,
},
},
data () {
return {
open: true,
isOpened: true,
icons: Object.freeze({
expand: expandIcon,
minimize: minimizeIcon,
@@ -189,13 +219,17 @@ export default {
isOpen () {
// Open status is a number so we can tell if the value was passed
if (this.openStatus !== undefined) return this.openStatus === 1;
return this.open;
return this.isOpened;
},
},
methods: {
toggle () {
this.open = !this.isOpen;
this.$emit('toggled', this.open);
this.isOpened = !this.isOpen;
this.$emit('toggled', this.isOpened);
},
open () {
this.isOpened = true;
this.$emit('toggled', this.isOpened);
},
},
};

View File

@@ -0,0 +1,99 @@
<template>
<span
v-if="show"
class="badge badge-round badge-item badge-equip"
:class="{'is-equipped': equipped === true}"
@click.stop="click"
>
<div
class="svg-icon color equip-icon"
v-html="icons.equip"
v-once
>
</div>
<div
class="svg-icon color unequip-icon"
v-html="icons.unEquip"
v-once
>
</div>
</span>
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
.badge-equip {
cursor: pointer;
display: none;
left: -9px;
color: $gray-200;
background: $white;
padding: 0.375rem;
.unequip-icon {
display: none;
}
&.is-equipped {
display: block;
background: $teal-50;
color: $white;
&:hover {
.unequip-icon {
display: block;
color: $maroon-50;
}
.equip-icon {
display: none;
}
background: $white;
}
}
&:hover:not(.is-equipped) {
color: $purple-400;
}
.svg-icon {
width: 100%;
height: 100%;
}
}
.item:hover > .badge-equip {
display: block;
}
</style>
<script>
import svgEquip from '@/assets/svg/equip.svg';
import svgUnEquip from '@/assets/svg/unequip.svg';
export default {
props: {
show: {
type: Boolean,
},
equipped: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
equip: svgEquip,
unEquip: svgUnEquip,
}),
};
},
methods: {
click () {
this.$emit('click');
},
},
};
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div>
<h4>
{{ title }}
</h4>
<div class="form-group">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'filterGroup',
props: ['title'],
};
</script>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
h4 {
min-height: 1.5rem;
}
.form-group ::v-deep {
.form-check {
margin-bottom: 0.5rem;
}
.custom-checkbox {
// min height for longer labels
min-height: 1.5rem;
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<div class="filter">
<slot name="header"></slot>
<slot name="search"></slot>
<div class="form">
<h3 v-once class="filter-label">
{{ $t('filters') }}
</h3>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'filterSidebar',
};
</script>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.filter-label {
min-height: 1.5rem;
margin-bottom: 0.25rem;
}
</style>

View File

@@ -17,13 +17,11 @@
{{ noItemsLabel }}
</p>
</div>
<div
<show-more-button
v-if="items.length > itemsPerRow"
class="btn btn-flat btn-show-more"
@click="toggleItemsToShow()"
>
{{ showAll ? $t('showLess') : $t('showMore') }}
</div>
:show-all="showAll"
/>
<div
v-else
class="fill-height"
@@ -32,12 +30,14 @@
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.fill-height {
height: 38px; // button + margin + padding
}
.item-rows {
margin-right: -24px;
margin-right: -1.5rem;
}
</style>
@@ -45,9 +45,11 @@
import _take from 'lodash/take';
import ResizeDirective from '@/directives/resize.directive';
import openedItemRowsMixin from '@/mixins/openedItemRows';
import ShowMoreButton from '@/components/ui/showMoreButton';
export default {
components: { ShowMoreButton },
directives: {
resize: ResizeDirective,
},

View File

@@ -1,9 +1,11 @@
<template>
<div>
<b-dropdown
class="inline-dropdown"
class="select-list"
:class="{'inline-dropdown':inlineDropdown}"
:toggle-class="isOpened ? 'active' : null"
:disabled="disabled"
:right="right"
@show="isOpened = true"
@hide="isOpened = false"
>
@@ -21,7 +23,12 @@
v-for="item in items"
:key="keyProp ? item[keyProp] : item"
:disabled="typeof item[disabledProp] === 'undefined' ? false : item[disabledProp]"
:class="{active: item === selected, selectListItem: true}"
:active="item === selected"
:class="{
active: item === selected,
selectListItem: true,
showIcon: !hideIcon && item === selected
}"
@click="selectItem(item)"
>
<slot
@@ -32,6 +39,12 @@
<!-- Fallback content -->
{{ item }}
</slot>
<div
v-once
class="svg-icon color check-icon"
v-html="icons.check"
></div>
</b-dropdown-item>
</b-dropdown>
</div>
@@ -40,9 +53,30 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.select-list ::v-deep .selectListItem {
position: relative;
&:not(.showIcon) {
.svg-icon.check-icon {
display: none;
}
}
.svg-icon.check-icon.color {
position: absolute;
right: 0.855rem;
top: 0.688rem;
bottom: 0.688rem;
width: 0.77rem;
height: 0.615rem;
color: $purple-300;
}
}
</style>
<script>
import svgCheck from '@/assets/svg/check.svg';
export default {
props: {
items: {
@@ -58,11 +92,24 @@ export default {
disabledProp: {
type: String,
},
hideIcon: {
type: Boolean,
},
right: {
type: Boolean,
},
inlineDropdown: {
type: Boolean,
default: true,
},
},
data () {
return {
isOpened: false,
selected: this.value,
icons: Object.freeze({
check: svgCheck,
}),
};
},
methods: {

View File

@@ -0,0 +1,41 @@
<template>
<button class="btn btn-flat btn-show-more mb-4"
@click="$emit('click')"
>
<span class="button-text">
{{ showAll ? $t('showLess') : $t('showMore') }}
</span>
</button>
</template>
<script>
export default {
props: ['showAll'],
};
</script>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.btn-show-more {
height: 2rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border-radius: 2px;
.button-text {
height: 1.5rem;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
text-align: center;
color: $gray-100;
}
&:hover {
.button-text {
color: $purple-300;
}
}
}
</style>

View File

@@ -0,0 +1,39 @@
/* eslint-disable import/no-extraneous-dependencies */
import { storiesOf } from '@storybook/vue';
import { text, withKnobs } from '@storybook/addon-knobs';
import toggleSwitch from './toggleSwitch';
const stories = storiesOf('Toggle Switch', module);
stories.addDecorator(withKnobs);
stories
.add('label only', () => ({
components: { toggleSwitch },
template: `
<div style="position: absolute; margin: 20px">
<toggle-switch :label="label"></toggle-switch>
</div>
`,
props: {
label: {
default: text('Label', 'example text'),
},
},
}))
.add('with description', () => ({
components: { toggleSwitch },
template: `
<div style="position: absolute; margin: 20px">
<toggle-switch :label="label" :hover-text="description"></toggle-switch>
</div>
`,
props: {
label: {
default: text('Label', 'example text'),
},
description: {
default: text('Description', 'description text'),
},
},
}));

View File

@@ -1,16 +1,23 @@
<template>
<div class="popover-box">
<div
:id="containerId"
class="clearfix toggle-switch-outer"
>
<div
v-if="label"
class="float-left toggle-switch-description"
:class="hoverText ? 'hasPopOver' : ''"
:class="{'bold': boldLabel}"
>
<span>{{ label }}</span>
</div>
<span
v-if="hoverText"
:id="containerId"
class="svg-icon inline icon-16 float-left"
v-html="icons.information"
>
</span>
<div class="toggle-switch float-left">
<input
:id="toggleId"
@@ -51,11 +58,16 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.toggle-switch-outer {
display: flex;
align-items: center;
}
.toggle-switch {
position: relative;
width: 40px;
user-select: none;
margin-left: 9px;
margin-left: 0.5rem;
}
.toggle-switch-description {
@@ -64,6 +76,10 @@
}
}
.svg-icon {
margin: 2px 0.5rem 2px 0.5rem;
}
.toggle-switch-checkbox {
display: none;
}
@@ -74,7 +90,7 @@
cursor: pointer;
border-radius: 100px;
margin-bottom: 0px;
margin-top: 3px;
margin-top: 2px;
}
.toggle-switch-inner {
@@ -110,7 +126,7 @@
display: block;
width: 20px;
margin: -2px;
margin-top: 1px;
margin-top: 0;
height: 20px;
background: $white;
position: absolute;
@@ -133,9 +149,15 @@
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
right: 0px;
}
.bold {
font-weight: bold;
}
</style>
<script>
import svgInformation from '@/assets/svg/information.svg';
export default {
model: {
prop: 'checked',
@@ -148,6 +170,10 @@ export default {
label: {
type: String,
},
boldLabel: {
type: Boolean,
default: false,
},
checked: {
type: Boolean,
default: false,
@@ -163,6 +189,10 @@ export default {
// The container requires a unique id to link it to the pop-over
containerId: this.generateId(),
focused: false,
icons: Object.freeze({
information: svgInformation,
}),
};
},
computed: {

View File

@@ -488,10 +488,10 @@
margin-right: 1em;
button {
width: 40px;
height: 40px;
padding: .7em;
margin-right: .5em;
width: 32px;
height: 32px;
padding: 0.5em;
margin-right: 0.5em;
}
}

View File

@@ -6,6 +6,7 @@ import { togglePinnedItem as togglePinnedItemOp } from '@/../../common/script/op
import changeClassOp from '@/../../common/script/ops/changeClass';
import disableClassesOp from '@/../../common/script/ops/disableClasses';
import openMysteryItemOp from '@/../../common/script/ops/openMysteryItem';
import { unEquipByType } from '../../../../common/script/ops/unequip';
import markPMSRead from '../../../../common/script/ops/markPMSRead';
export function fetch (store, options = {}) { // eslint-disable-line no-shadow
@@ -223,3 +224,19 @@ export function newPrivateMessageTo (store, params) {
userStyles,
};
}
export async function unequip (store, params) {
const user = store.state.user.data;
unEquipByType(user, { params });
const response = await axios.post(`/api/v4/user/unequip/${params.type}`);
store.dispatch('snackbars:add', {
title: 'Habitica',
text: response.data.message,
type: 'info',
});
return response.data.data;
}

View File

@@ -5,7 +5,7 @@ describe('DrawerComponent', () => {
it('sets the correct default data', () => {
expect(DrawerComponent.data).to.be.a('function');
const defaultData = DrawerComponent.data();
expect(defaultData.open).to.be.true;
expect(defaultData.isOpened).to.be.true;
});
it('renders the correct title', () => {

View File

@@ -15,6 +15,7 @@
"selectWinner": "Select a winner and close the challenge:",
"endChallenge": "End Challenge",
"filter": "Filter",
"filters": "Filters",
"groups": "Groups",
"category": "Category",
"membership": "Membership",

View File

@@ -4,5 +4,7 @@
"eggsItemType": "Eggs",
"hatchingPotionsItemType": "Hatching Potions",
"specialItemType": "Special items",
"lockedItem": "Locked Item"
"lockedItem": "Locked Item",
"petAndMount": "Pet and Mount",
"allItems": "All Items"
}

View File

@@ -12,6 +12,11 @@
"messageDontEnjoyFood": "<%= egg %> eats <%= foodText %> but doesn't seem to enjoy it.",
"messageBought": "Bought <%= itemText %>",
"messageUnEquipped": "<%= itemText %> unequipped.",
"messageBattleGearUnEquipped": "Battle Gear unequipped.",
"messageCostumeUnEquipped": "Costume unequipped.",
"messagePetMountUnEquipped": "Pet and Mount unequipped.",
"messageBackgroundUnEquipped": "Background unequipped.",
"messageAllUnEquipped": "Everything unequipped.",
"messageMissingEggPotion": "You're missing either that egg or that potion",
"messageInvalidEggPotionCombo": "You can't hatch Quest Pet Eggs with Magic Hatching Potions! Try a different egg.",
"messageAlreadyPet": "You already have that pet. Try hatching a different combination!",

View File

@@ -87,6 +87,7 @@ import unlock from './ops/unlock';
import updateTask from './ops/updateTask';
// TODO under api.libs.statHelpers?
import * as statHelpers from './statHelpers';
import { unEquipByType } from './ops/unequip';
const api = {};
api.content = content;
@@ -182,6 +183,7 @@ api.ops = {
reset,
markPmsRead,
pinnedGearUtils,
unEquipByType,
};
api.errorMessages = {

View File

@@ -31,6 +31,7 @@ import openMysteryItem from './openMysteryItem';
import scoreTask from './scoreTask';
import markPmsRead from './markPMSRead';
import * as pinnedGearUtils from './pinnedGearUtils';
import { unEquipByType } from './unequip';
export default {
sleep,
@@ -66,4 +67,5 @@ export default {
scoreTask,
markPmsRead,
pinnedGearUtils,
unEquipByType,
};

View File

@@ -0,0 +1,78 @@
import get from 'lodash/get';
import content from '../content/index';
import i18n from '../i18n';
import {
BadRequest,
} from '../libs/errors';
import errorMessage from '../libs/errorMessage';
export const UNEQUIP_PET_MOUNT = 'pet';
export const UNEQUIP_BACKGROUND = 'background';
export const UNEQUIP_ALL = 'all';
export const UNEQUIP_COSTUME = 'costume';
export const UNEQUIP_EQUIPPED = 'equipped';
const allTypes = [
UNEQUIP_PET_MOUNT, UNEQUIP_BACKGROUND,
UNEQUIP_ALL, UNEQUIP_COSTUME, UNEQUIP_EQUIPPED,
];
function unequipEquipment (user, type) {
for (const gearType of content.gearTypes) {
user.items.gear[type][gearType] = `${gearType}_base_0`;
}
}
export function unEquipByType (user, req = {}) {
// Being type a parameter followed by another parameter
// when using the API it must be passes specifically in the URL, it's won't default to equipped
const type = get(req, 'params.type', 'equipped');
if (allTypes.indexOf(type) === -1) {
throw new BadRequest(errorMessage('invalidTypeEquip'));
}
let message;
switch (type) { // eslint-disable-line default-case
case UNEQUIP_PET_MOUNT: {
user.items.currentPet = '';
user.items.currentMount = '';
message = i18n.t('messagePetMountUnEquipped', {}, req.language);
break;
}
case UNEQUIP_BACKGROUND: {
user.preferences.background = '';
message = i18n.t('messageBackgroundUnEquipped', {}, req.language);
break;
}
case UNEQUIP_ALL: {
user.items.currentMount = '';
user.items.currentPet = '';
user.preferences.background = '';
unequipEquipment(user, UNEQUIP_COSTUME);
unequipEquipment(user, UNEQUIP_EQUIPPED);
message = i18n.t('messageAllUnEquipped', {}, req.language);
break;
}
case UNEQUIP_COSTUME:
case UNEQUIP_EQUIPPED: {
unequipEquipment(user, type);
message = i18n.t(type === UNEQUIP_COSTUME
? 'messageCostumeUnEquipped'
: 'messageBattleGearUnEquipped', {}, req.language);
break;
}
}
const res = [user.items];
if (message) res.push(message);
return res;
}

View File

@@ -1,6 +1,7 @@
import { authWithHeaders } from '../../middlewares/auth';
import * as userLib from '../../libs/user';
import { verifyDisplayName } from '../../libs/user/validation';
import common from '../../../common';
const api = {};
@@ -244,4 +245,38 @@ api.verifyDisplayName = {
},
};
/**
* @api {post} /api/v4/user/unequip/:type Unequip all items by type
* @apiName UserUnEquipByType
* @apiGroup User
*
* @apiParam (Path) {String="pet-mount-background","costume","equipped"} type The type of items
* to unequip.
*
* @apiParamExample {URL} Example-URL
* https://habitica.com/api/v4/user/unequip/equipped
*
* @apiSuccess {Object} data user.items
* @apiSuccess {String} message Optional success message for unequipping an items
*
* @apiSuccessExample {json} Example return:
* {
* "success": true,
* "data": {---DATA TRUNCATED---},
* "message": "Battle Gear unequipped.
* }
*
*/
api.unequip = {
method: 'POST',
middlewares: [authWithHeaders()],
url: '/user/unequip/:type',
async handler (req, res) {
const { user } = res.locals;
const equipRes = common.ops.unEquipByType(user, req);
await user.save();
res.respond(200, ...equipRes);
},
};
export default api;