mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
[WIP] client/inventory/stable (#8737)
* Stable: basic layout (based on equipment) * extract item popover-content as component # Conflicts: # website/client/components/inventory/item.vue * extract item-content as slot - list standard pets with image * dynamic list petGroups in sidebar / content - with selected / open filter * itemContentClass for pets * fix filter - extract filter labels * Filter: Hide Missing * fix position of pets * sort by: A-Z, Color, Hatchable * refactor animalList - created the list once per type and cache it - sort now works before viewing one or all pets * custom petItem to show the progress * list specialPets - rename petGroup to animalGroup (more generic) * equip a pet * filter animals by input * count pets * list mounts * hatchable pet popover * hatchable pet popover #2 * PixelPaw Opacity for not owned and not hatchable pets - change item background for unowned pets * Hold to hatch the pet - cleanup * add food drawer + countBadge - special mounts - hide un-owned special animals - fixes * Sliding Drawer: Buttons to scroll left/right * Drag&Drop: food on pets * fix hold to hatch - use mouseleave event * 'Show All' / 'Show Less' - Animals * Matts Image + Popover + use image width as sidebar width (removed col-2 / col-10) * fixes: colors, v-once, drawer-errorMessage, strings * drawerSlider - split items to pages / add divider / add first item of the next page - ResizeDirective * ResizeDirective - throttle emits by `v-resize="value"` - fix drawer left padding * show animals by available content width * change sortBy button / label * fix pet colors / backgrounds * DragDropDirective - grabbing cursor * remove browser specific prefixes * fix lint issues * show welcome dialog * change translationkey (noFood, already exists)
This commit is contained in:
9
website/client/assets/scss/dragdrop.scss
Normal file
9
website/client/assets/scss/dragdrop.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
[draggable] {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
|
||||
[draggable]:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
@@ -20,4 +20,5 @@
|
||||
@import './popover';
|
||||
@import './item';
|
||||
@import './stats';
|
||||
@import './icon';
|
||||
@import './icon';
|
||||
@import './dragdrop';
|
||||
|
||||
3
website/client/assets/svg/next.svg
Normal file
3
website/client/assets/svg/next.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="20" viewBox="0 0 13 20">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="4" d="M2 2l8 8-8 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 186 B |
3
website/client/assets/svg/previous.svg
Normal file
3
website/client/assets/svg/previous.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="20" viewBox="0 0 13 20">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="4" d="M11 2l-8 8 8 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
@@ -91,8 +91,7 @@
|
||||
}
|
||||
|
||||
.drawer-slider {
|
||||
padding: 12px 0 0 24px;
|
||||
margin-left: -24px;
|
||||
padding: 12px 0 0 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -16,13 +16,16 @@ b-popover(
|
||||
.item-wrapper
|
||||
.item
|
||||
slot(name="itemBadge", :item="item")
|
||||
span.item-content(:class="itemContentClass")
|
||||
span.item-content(
|
||||
:class="itemContentClass",
|
||||
:draggable="draggable",
|
||||
@dragstart="onDrag"
|
||||
)
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -46,16 +49,22 @@ export default {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click () {
|
||||
this.$emit('click', this.item);
|
||||
},
|
||||
onDrag (ev) {
|
||||
if (this.draggable) {
|
||||
this.$emit('onDrag', ev);
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.col-2.standard-sidebar
|
||||
.form-group
|
||||
input.form-control(type="text", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h3(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
.form-check
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
strong {{ $t('pets') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('hatchingPotions') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('quest') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('special') }}
|
||||
.form-group
|
||||
.form-check
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
strong {{ $t('mounts') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('hatchingPotions') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('quest') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('special') }}
|
||||
|
||||
.col-10.standard-page
|
||||
h4 Pets
|
||||
.inventory-item-container(v-for="pet in dropPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.dropPets = !open.dropPets") {{ open.dropPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Magic Potions Pets
|
||||
.inventory-item-container(v-for="pet in magicPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.magicPets = !open.magicPets") {{ open.magicPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Quest Pets
|
||||
.inventory-item-container(v-for="pet in questPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.questPets = !open.questPets") {{ open.questPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Rare Pets
|
||||
.inventory-item-container(v-for="pet in rarePets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.rarePets = !open.rarePets") {{ open.rarePets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Mounts
|
||||
h2 Quest Mounts
|
||||
h2 Rare Mounts
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.inventory-item-container {
|
||||
padding: 20px;
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import each from 'lodash/each';
|
||||
|
||||
// TODO Normalize special pets and mounts
|
||||
// import Store from 'client/store';
|
||||
// import deepFreeze from 'client/libs/deepFreeze';
|
||||
// const specialMounts =
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
open: {
|
||||
dropPets: false,
|
||||
magicPets: false,
|
||||
questPets: false,
|
||||
rarePets: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['content']),
|
||||
dropPets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.dropPets);
|
||||
},
|
||||
magicPets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.premiumHatchingPotions, this.open.magicPets);
|
||||
},
|
||||
questPets () {
|
||||
return this.listAnimals('pet', this.content.questEggs, this.content.dropHatchingPotions, this.open.questPets);
|
||||
},
|
||||
rarePets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.rarePets);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
listAnimals (type, eggSource, potionSource, isOpen = false) {
|
||||
let animals = [];
|
||||
let iteration = 0;
|
||||
|
||||
each(eggSource, (egg) => {
|
||||
if (iteration === 1 && !isOpen) return false;
|
||||
iteration++;
|
||||
each(potionSource, (potion) => {
|
||||
let animalKey = `${egg.key}-${potion.key}`;
|
||||
animals.push(this.content[`${type}Info`][animalKey].text());
|
||||
});
|
||||
});
|
||||
|
||||
return animals;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
33
website/client/components/inventory/stable/countBadge.vue
Normal file
33
website/client/components/inventory/stable/countBadge.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template lang="pug">
|
||||
span.badge.badge-pill.badge-item.badge-count(
|
||||
v-if="show",
|
||||
) {{ count }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-count {
|
||||
right: -9px;
|
||||
color: $white;
|
||||
background: $orange-100;
|
||||
padding: 4.5px 6px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
174
website/client/components/inventory/stable/drawerSlider.vue
Normal file
174
website/client/components/inventory/stable/drawerSlider.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template lang="pug">
|
||||
div.slider-root(
|
||||
v-bind:class="{'scrollButtonsVisible': scrollButtonsVisible}",
|
||||
)
|
||||
div.slider-button-area.left-button(
|
||||
v-if="scrollButtonsVisible",
|
||||
@mousedown.left="lastPage"
|
||||
)
|
||||
a.slider-button
|
||||
.svg-icon(v-html="icons.previous")
|
||||
div.slider-button-area.right-button(
|
||||
v-if="scrollButtonsVisible",
|
||||
@mousedown.left="nextPage"
|
||||
)
|
||||
a.slider-button
|
||||
.svg-icon(v-html="icons.next")
|
||||
|
||||
// 120 = width of the left/right buttons
|
||||
div.sliding-content(v-resize="500", @resized="currentWidth = $event.width - 120")
|
||||
.items.items-one-line
|
||||
template(v-for="item in pages[currentPage]")
|
||||
div.vertical-divider(v-if="item.ofNextPage")
|
||||
|
||||
slot(
|
||||
name="item",
|
||||
:item="item",
|
||||
)
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
$buttonAreaWidth: 60;
|
||||
|
||||
.slider-root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slider-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
position: absolute;
|
||||
top: calc((100% - 40px) / 2);
|
||||
|
||||
.svg-icon {
|
||||
color: #a5a1ac;
|
||||
margin: auto 0;
|
||||
position: absolute;
|
||||
top: calc((100% - 12px) / 2);
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollButtonsVisible {
|
||||
|
||||
.sliding-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-button-area {
|
||||
width: $buttonAreaWidth+px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
&.left-button {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right-button {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sliding-content .items {
|
||||
padding-top: 10px;
|
||||
margin-left: $buttonAreaWidth+ px;
|
||||
|
||||
& > div:last-of-type {
|
||||
margin-right: $buttonAreaWidth + 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-divider {
|
||||
height: 92px;
|
||||
width: 1px;
|
||||
background: #34313a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import previous from 'assets/svg/previous.svg';
|
||||
import next from 'assets/svg/next.svg';
|
||||
import ResizeDirective from 'client/directives/resize.directive';
|
||||
|
||||
import _chunk from 'lodash/chunk';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
resize: ResizeDirective,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
previous,
|
||||
next,
|
||||
}),
|
||||
currentWidth: 0,
|
||||
currentPage: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
return _chunk(this.items, this.itemsPerPage() - 1).map((content, index, array) => {
|
||||
let resultData = [...content];
|
||||
|
||||
if (array[index + 1]) {
|
||||
resultData.push({
|
||||
...array[index + 1][0],
|
||||
ofNextPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
return resultData;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
lastPage () {
|
||||
if (this.currentPage > 0) {
|
||||
this.currentPage--;
|
||||
} else {
|
||||
this.currentPage = this.pages.length - 1;
|
||||
}
|
||||
},
|
||||
|
||||
nextPage () {
|
||||
if (this.currentPage < this.pages.length - 1) {
|
||||
this.currentPage++;
|
||||
} else {
|
||||
this.currentPage = 0;
|
||||
}
|
||||
},
|
||||
|
||||
itemsPerPage () {
|
||||
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
|
||||
},
|
||||
},
|
||||
props: {
|
||||
scrollButtonsVisible: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
},
|
||||
itemWidth: {
|
||||
type: Number,
|
||||
},
|
||||
itemMargin: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
48
website/client/components/inventory/stable/foodItem.vue
Normal file
48
website/client/components/inventory/stable/foodItem.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template lang="pug">
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'top'",
|
||||
)
|
||||
span(slot="content")
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
div.popover-content-text(v-html="item.notes()")
|
||||
|
||||
.item-wrapper
|
||||
.item
|
||||
countBadge(
|
||||
:show="true",
|
||||
:count="itemCount"
|
||||
)
|
||||
span.item-content(
|
||||
:class="'Pet_Food_'+item.key",
|
||||
v-drag.food="item.key"
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import DragDropDirective from 'client/directives/dragdrop.directive';
|
||||
|
||||
import CountBadge from './countBadge';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
CountBadge,
|
||||
},
|
||||
directives: {
|
||||
drag: DragDropDirective,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
itemCount: {
|
||||
type: Number,
|
||||
},
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
721
website/client/components/inventory/stable/index.vue
Normal file
721
website/client/components/inventory/stable/index.vue
Normal file
@@ -0,0 +1,721 @@
|
||||
<template lang="pug">
|
||||
.row.stable
|
||||
.standard-sidebar
|
||||
div
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'right'"
|
||||
)
|
||||
span(slot="content")
|
||||
h4.popover-content-title(v-once) {{ $t('mattBoch') }}
|
||||
.popover-content-text(v-once) {{ $t('mattBochText1') }}
|
||||
|
||||
div.npc_matt
|
||||
|
||||
.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('pets') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="petGroup in petGroups",
|
||||
v-if="viewOptions[petGroup.key].animalCount != 0",
|
||||
:key="petGroup.key"
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[petGroup.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ petGroup.label }}
|
||||
h3(v-once) {{ $t('mounts') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="mountGroup in mountGroups",
|
||||
v-if="viewOptions[mountGroup.key].animalCount != 0",
|
||||
:key="mountGroup.key"
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[mountGroup.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ mountGroup.label }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left Hide Missing
|
||||
toggle-switch.float-right.hideMissing(
|
||||
:label="''",
|
||||
:checked="hideMissing",
|
||||
@change="updateHideMissing"
|
||||
)
|
||||
|
||||
.standard-page(v-resize="500", @resized="availableContentWidth = $event.width - 48")
|
||||
.clearfix
|
||||
h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }}
|
||||
|
||||
div.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(selectedSortBy)", right=true)
|
||||
b-dropdown-item(
|
||||
v-for="sort in sortByItems",
|
||||
@click="selectedSortBy = sort",
|
||||
:active="selectedSortBy === sort",
|
||||
:key="sort"
|
||||
) {{ $t(sort) }}
|
||||
|
||||
h2
|
||||
| {{ $t('pets') }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
|
||||
|
||||
div(
|
||||
v-for="petGroup in petGroups",
|
||||
v-if="viewOptions[petGroup.key].selected",
|
||||
:key="petGroup.key"
|
||||
)
|
||||
h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }}
|
||||
|
||||
div.items
|
||||
div(
|
||||
v-for="pet in pets(petGroup, viewOptions[petGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
|
||||
:key="pet.key",
|
||||
v-drag.drop.food="pet.key",
|
||||
@dragover="onDragOver($event, pet)",
|
||||
@dropped="onDrop($event, pet)",
|
||||
)
|
||||
petItem(
|
||||
:item="pet",
|
||||
:itemContentClass="getPetItemClass(pet)",
|
||||
:popoverPosition="'top'",
|
||||
:progress="pet.progress",
|
||||
:emptyItem="!pet.isOwned()",
|
||||
:showPopover="pet.isOwned() || pet.isHatchable()",
|
||||
@hatchPet="hatchPet",
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
div(v-if="pet.isOwned()")
|
||||
h4.popover-content-title {{ pet.name }}
|
||||
div.hatchablePopover(v-else-if="pet.isHatchable()")
|
||||
h4.popover-content-title {{ pet.name }}
|
||||
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: pet.potionName, egg: pet.eggName })")
|
||||
div.potionEggGroup
|
||||
div.potionEggBackground
|
||||
div(:class="'Pet_HatchingPotion_'+pet.potionKey")
|
||||
div.potionEggBackground
|
||||
div(:class="'Pet_Egg_'+pet.eggKey")
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="ctx.item.key === currentPet",
|
||||
:show="ctx.item.isOwned()",
|
||||
@click="selectPet(ctx.item)",
|
||||
)
|
||||
|
||||
.btn.btn-show-more(
|
||||
@click="viewOptions[petGroup.key].open = !viewOptions[petGroup.key].open",
|
||||
v-if="viewOptions[petGroup.key].animalCount != 0"
|
||||
) {{ $t(viewOptions[petGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: petGroup.label, type: $t('pets')}) }}
|
||||
|
||||
h2
|
||||
| {{ $t('mounts') }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{countOwnedAnimals(mountGroups[0], 'mount')}}
|
||||
|
||||
div(
|
||||
v-for="mountGroup in mountGroups",
|
||||
v-if="viewOptions[mountGroup.key].selected",
|
||||
:key="mountGroup.key"
|
||||
)
|
||||
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
|
||||
|
||||
div.items
|
||||
item(
|
||||
v-for="mount in mounts(mountGroup, viewOptions[mountGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
|
||||
:item="mount",
|
||||
:itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw greyedOut'",
|
||||
:key="mount.key",
|
||||
:popoverPosition="'top'"
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
h4.popover-content-title {{ mount.name }}
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="ctx.item.key === currentMount",
|
||||
:show="mount.isOwned()",
|
||||
@click="selectMount(ctx.item)",
|
||||
)
|
||||
|
||||
.btn.btn-show-more(
|
||||
@click="viewOptions[mountGroup.key].open = !viewOptions[mountGroup.key].open",
|
||||
v-if="viewOptions[mountGroup.key].animalCount != 0"
|
||||
) {{ $t(viewOptions[mountGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: mountGroup.label, type: $t('mounts')}) }}
|
||||
|
||||
drawer(
|
||||
:title="$t('quickInventory')",
|
||||
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? $t('noFoodAvailable') : null"
|
||||
)
|
||||
div(slot="drawer-header")
|
||||
.drawer-tab-container
|
||||
.drawer-tab.text-right
|
||||
a.drawer-tab-text(
|
||||
@click="selectedDrawerTab = 0",
|
||||
:class="{'drawer-tab-text-active': selectedDrawerTab === 0}",
|
||||
) {{ drawerTabs[0].label }}
|
||||
.clearfix
|
||||
.drawer-tab.float-left
|
||||
a.drawer-tab-text(
|
||||
@click="selectedDrawerTab = 1",
|
||||
:class="{ 'drawer-tab-text-active': selectedDrawerTab === 1 }",
|
||||
) {{ drawerTabs[1].label }}
|
||||
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'top'"
|
||||
)
|
||||
span(slot="content")
|
||||
.popover-content-text Test Popover
|
||||
|
||||
div.float-right What does my pet like to eat?
|
||||
|
||||
|
||||
drawer-slider(
|
||||
:items="drawerTabs[selectedDrawerTab].items",
|
||||
:scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)",
|
||||
slot="drawer-slider",
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
foodItem(
|
||||
:item="ctx.item",
|
||||
:itemCount="userItems.food[ctx.item.key]",
|
||||
)
|
||||
|
||||
b-modal#welcome-modal(
|
||||
:ok-only="true",
|
||||
:ok-title="$t('gotIt')",
|
||||
:visible="!hideDialog",
|
||||
:hide-header="true"
|
||||
)
|
||||
div.content
|
||||
div.npc_matt
|
||||
h1.page-header(v-once) {{ $t('welcomeStable') }}
|
||||
div.content-text(v-once) {{ $t('welcomeStableText') }}
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.inventory-item-container {
|
||||
padding: 20px;
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.stable .item .item-content.Pet {
|
||||
position: absolute;
|
||||
top: -28px;
|
||||
}
|
||||
|
||||
.toggle-switch-container.hideMissing {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.hatchablePopover {
|
||||
width: 180px
|
||||
}
|
||||
|
||||
.potionEggGroup {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.potionEggBackground {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 2px;
|
||||
background-color: #4e4a57;
|
||||
|
||||
&: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;
|
||||
}
|
||||
|
||||
.npc_matt {
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
.stable {
|
||||
|
||||
.standard-page {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
// 3% padding + 252px sidebar width
|
||||
left: calc(3% + 252px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-slider .items {
|
||||
height: 114px;
|
||||
}
|
||||
|
||||
|
||||
div#welcome-modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
header, footer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.npc_matt {
|
||||
margin: 0 auto 21px auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
|
||||
// the modal already has 15px padding
|
||||
margin-left: 33px;
|
||||
margin-right: 33px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 1.43;
|
||||
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop.fade.show {
|
||||
background-color: $purple-50;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import _each from 'lodash/each';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _take from 'lodash/take';
|
||||
import _filter from 'lodash/filter';
|
||||
import _drop from 'lodash/drop';
|
||||
import _flatMap from 'lodash/flatMap';
|
||||
import _throttle from 'lodash/throttle';
|
||||
|
||||
import Item from '../item';
|
||||
import PetItem from './petItem';
|
||||
import FoodItem from './foodItem';
|
||||
import Drawer from 'client/components/inventory/drawer';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import StarBadge from 'client/components/inventory/starBadge';
|
||||
import CountBadge from './countBadge';
|
||||
import DrawerSlider from './drawerSlider';
|
||||
|
||||
import ResizeDirective from 'client/directives/resize.directive';
|
||||
import DragDropDirective from 'client/directives/dragdrop.directive';
|
||||
|
||||
// TODO Normalize special pets and mounts
|
||||
// import Store from 'client/store';
|
||||
// import deepFreeze from 'client/libs/deepFreeze';
|
||||
// const specialMounts =
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PetItem,
|
||||
Item,
|
||||
FoodItem,
|
||||
Drawer,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
bPopover,
|
||||
bModal,
|
||||
toggleSwitch,
|
||||
StarBadge,
|
||||
CountBadge,
|
||||
DrawerSlider,
|
||||
},
|
||||
directives: {
|
||||
resize: ResizeDirective,
|
||||
drag: DragDropDirective,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
hideMissing: false,
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: '',
|
||||
|
||||
// sort has the translation-keys as values
|
||||
selectedSortBy: 'standard',
|
||||
sortByItems: [
|
||||
'standard',
|
||||
'AZ',
|
||||
'sortByColor',
|
||||
'sortByHatchable',
|
||||
],
|
||||
|
||||
selectedDrawerTab: 0,
|
||||
availableContentWidth: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
let search = this.searchText.toLowerCase();
|
||||
|
||||
this.searchTextThrottled = search;
|
||||
}, 250),
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
currentPet: 'user.data.items.currentPet',
|
||||
currentMount: 'user.data.items.currentMount',
|
||||
userItems: 'user.data.items',
|
||||
hideDialog: 'user.data.flags.tutorial.common.mounts',
|
||||
}),
|
||||
|
||||
petGroups () {
|
||||
let petGroups = [
|
||||
{
|
||||
label: this.$t('filterByStandard'),
|
||||
key: 'standardPets',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByMagicPotion'),
|
||||
key: 'magicPets',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.premiumHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByQuest'),
|
||||
key: 'questPets',
|
||||
petSource: {
|
||||
eggs: this.content.questEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
key: 'specialPets',
|
||||
petSource: {
|
||||
special: this.content.specialPets,
|
||||
},
|
||||
alwaysHideMissing: true,
|
||||
},
|
||||
];
|
||||
|
||||
petGroups.map((petGroup) => {
|
||||
this.$set(this.viewOptions, petGroup.key, {
|
||||
selected: true,
|
||||
open: false,
|
||||
animalCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return petGroups;
|
||||
},
|
||||
mountGroups () {
|
||||
let mountGroups = [
|
||||
{
|
||||
label: this.$t('filterByStandard'),
|
||||
key: 'standardMounts',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByMagicPotion'),
|
||||
key: 'magicMounts',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.premiumHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByQuest'),
|
||||
key: 'questMounts',
|
||||
petSource: {
|
||||
eggs: this.content.questEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
key: 'specialMounts',
|
||||
petSource: {
|
||||
special: this.content.specialMounts,
|
||||
},
|
||||
alwaysHideMissing: true,
|
||||
},
|
||||
];
|
||||
|
||||
mountGroups.map((mountGroup) => {
|
||||
this.$set(this.viewOptions, mountGroup.key, {
|
||||
selected: true,
|
||||
open: false,
|
||||
animalCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return mountGroups;
|
||||
},
|
||||
|
||||
drawerTabs () {
|
||||
return [
|
||||
{
|
||||
label: this.$t('food'),
|
||||
items: _filter(this.content.food, f => {
|
||||
return f.key !== 'Saddle' && this.userItems.food[f.key];
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
items: _filter(this.content.food, f => {
|
||||
return f.key === 'Saddle' && this.userItems.food[f.key];
|
||||
}),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
getAnimalList (animalGroup, type) {
|
||||
let key = animalGroup.key;
|
||||
|
||||
this.cachedAnimalList = this.cachedAnimalList || {};
|
||||
if (this.cachedAnimalList[key]) {
|
||||
return this.cachedAnimalList[key];
|
||||
}
|
||||
|
||||
let animals = [];
|
||||
let userItems = this.userItems;
|
||||
|
||||
switch (key) {
|
||||
case 'specialPets':
|
||||
case 'specialMounts': {
|
||||
_each(animalGroup.petSource.special, (value, specialKey) => {
|
||||
let eggKey = specialKey.split('-')[0];
|
||||
let potionKey = specialKey.split('-')[1];
|
||||
|
||||
animals.push({
|
||||
key: specialKey,
|
||||
eggKey,
|
||||
potionKey,
|
||||
pet: this.content[`${type}Info`][specialKey].text(),
|
||||
isOwned () {
|
||||
return [`${type}s`][this.key] > 0;
|
||||
},
|
||||
isHatchable () {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
_each(animalGroup.petSource.eggs, (egg) => {
|
||||
_each(animalGroup.petSource.potions, (potion) => {
|
||||
let animalKey = `${egg.key}-${potion.key}`;
|
||||
|
||||
animals.push({
|
||||
key: animalKey,
|
||||
eggKey: egg.key,
|
||||
eggName: egg.text(),
|
||||
potionKey: potion.key,
|
||||
potionName: potion.text(),
|
||||
name: this.content[`${type}Info`][animalKey].text(),
|
||||
isOwned () {
|
||||
return userItems[`${type}s`][animalKey] > 0;
|
||||
},
|
||||
isHatchable () {
|
||||
return userItems.eggs[egg.key] > 0 && userItems.hatchingPotions[potion.key] > 0;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedAnimalList[key] = animals;
|
||||
|
||||
return animals;
|
||||
},
|
||||
|
||||
listAnimals (animalGroup, type, isOpen, hideMissing, sort, searchText, availableSpace) {
|
||||
let animals = this.getAnimalList(animalGroup, type);
|
||||
let isPetList = type === 'pet';
|
||||
let withProgress = isPetList && animalGroup.key !== 'specialPets';
|
||||
|
||||
// 1. Filter
|
||||
if (hideMissing || animalGroup.alwaysHideMissing) {
|
||||
animals = _filter(animals, (a) => {
|
||||
return a.isOwned();
|
||||
});
|
||||
}
|
||||
|
||||
if (searchText && searchText !== '') {
|
||||
animals = _filter(animals, (a) => {
|
||||
return a.name.toLowerCase().indexOf(searchText) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Sort
|
||||
switch (sort) {
|
||||
case 'AZ':
|
||||
animals = _sortBy(animals, ['pet']);
|
||||
break;
|
||||
|
||||
case 'sortByColor':
|
||||
animals = _sortBy(animals, ['potionName']);
|
||||
break;
|
||||
|
||||
case 'sortByHatchable': {
|
||||
if (isPetList) {
|
||||
let sortFunc = (i) => {
|
||||
return i.isHatchable() ? 0 : 1;
|
||||
};
|
||||
|
||||
animals = _sortBy(animals, [sortFunc]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let animalRows = [];
|
||||
|
||||
let itemsPerRow = Math.floor(availableSpace / (94 + 24));
|
||||
|
||||
let rowsToShow = isOpen ? Math.ceil(animals.length / itemsPerRow) : 1;
|
||||
|
||||
for (let i = 0; i < rowsToShow; i++) {
|
||||
let skipped = _drop(animals, i * itemsPerRow);
|
||||
let row = _take(skipped, itemsPerRow);
|
||||
|
||||
let rowWithProgressData = withProgress ? _flatMap(row, (a) => {
|
||||
let progress = this.userItems[`${type}s`][a.key];
|
||||
|
||||
return {
|
||||
...a,
|
||||
progress,
|
||||
};
|
||||
}) : row;
|
||||
|
||||
animalRows.push(...rowWithProgressData);
|
||||
}
|
||||
|
||||
this.viewOptions[animalGroup.key].animalCount = animals.length;
|
||||
|
||||
return animalRows;
|
||||
},
|
||||
|
||||
countOwnedAnimals (animalGroup, type) {
|
||||
let animals = this.getAnimalList(animalGroup, type);
|
||||
|
||||
let countAll = animals.length;
|
||||
let countOwned = _filter(animals, (a) => {
|
||||
return a.isOwned();
|
||||
});
|
||||
|
||||
return `${countOwned.length}/${countAll}`;
|
||||
},
|
||||
|
||||
pets (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
|
||||
return this.listAnimals(animalGroup, 'pet', showAll, hideMissing, sortBy, searchText, availableSpace);
|
||||
},
|
||||
|
||||
mounts (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
|
||||
return this.listAnimals(animalGroup, 'mount', showAll, hideMissing, sortBy, searchText, availableSpace);
|
||||
},
|
||||
|
||||
getPetItemClass (pet) {
|
||||
if (pet.isOwned()) {
|
||||
return `Pet Pet-${pet.key}`;
|
||||
}
|
||||
|
||||
if (pet.isHatchable()) {
|
||||
return 'PixelPaw';
|
||||
}
|
||||
|
||||
return 'GreyedOut PixelPaw';
|
||||
},
|
||||
|
||||
hasDrawerTabItems (index) {
|
||||
return this.drawerTabs && this.drawerTabs[index].items.length !== 0;
|
||||
},
|
||||
|
||||
// Actions
|
||||
updateHideMissing (newVal) {
|
||||
this.hideMissing = newVal;
|
||||
},
|
||||
|
||||
selectPet (item) {
|
||||
this.$store.dispatch('common:equip', {key: item.key, type: 'pet'});
|
||||
},
|
||||
|
||||
selectMount (item) {
|
||||
this.$store.dispatch('common:equip', {key: item.key, type: 'mount'});
|
||||
},
|
||||
|
||||
hatchPet (pet) {
|
||||
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
|
||||
},
|
||||
|
||||
onDragOver (ev, pet) {
|
||||
if (this.userItems.mounts[pet.key]) {
|
||||
ev.dropable = false;
|
||||
}
|
||||
},
|
||||
|
||||
onDrop (ev, pet) {
|
||||
this.$store.dispatch('common:feed', {pet: pet.key, food: ev.draggingKey});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
117
website/client/components/inventory/stable/petItem.vue
Normal file
117
website/client/components/inventory/stable/petItem.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template lang="pug">
|
||||
b-popover(
|
||||
:triggers="[showPopover?'hover':'']",
|
||||
:placement="popoverPosition",
|
||||
)
|
||||
span(slot="content")
|
||||
slot(name="popoverContent", :item="item")
|
||||
|
||||
.item-wrapper
|
||||
.item(
|
||||
:class="{'item-empty': emptyItem}",
|
||||
@mouseup="holdStop",
|
||||
@mouseleave="holdStop",
|
||||
@mousedown.left="holdStart"
|
||||
)
|
||||
slot(name="itemBadge", :item="item")
|
||||
span.item-content(:class="itemContentClass")
|
||||
span.pet-progress-background(v-if="progress > 0")
|
||||
div.pet-progress-bar(v-bind:style="{width: 100 * progress/50 + '%' }")
|
||||
span.pet-progress-background(v-if="holdProgress > 0")
|
||||
div.pet-progress-bar.hold(v-bind:style="{width: 100 * holdProgress/5 + '%' }")
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.pet-progress-background {
|
||||
width: 62px;
|
||||
height: 4px;
|
||||
background-color: #e1e0e3;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: calc((100% - 62px) / 2);
|
||||
}
|
||||
|
||||
.pet-progress-bar {
|
||||
height: 4px;
|
||||
background-color: #24cc8f;
|
||||
}
|
||||
|
||||
.pet-progress-bar.hold {
|
||||
background-color: #54c3cc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
emptyItem: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popoverPosition: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
showPopover: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
holdProgress: -1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
holdStart () {
|
||||
let pet = this.item;
|
||||
if (pet.isOwned() || !pet.isHatchable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.holdProgress = 1;
|
||||
|
||||
this.currentHoldingTimer = setInterval(() => {
|
||||
if (this.holdProgress === 5) {
|
||||
this.holdStop();
|
||||
this.$emit('hatchPet', pet);
|
||||
}
|
||||
|
||||
this.holdProgress += 1;
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
holdStop () {
|
||||
if (this.currentHoldingTimer) {
|
||||
clearInterval(this.currentHoldingTimer);
|
||||
this.holdProgress = -1;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
8
website/client/directives/directive.common.js
Normal file
8
website/client/directives/directive.common.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// https://stackoverflow.com/a/40720172/1298154
|
||||
export const emit = (vnode, emitName, data) => {
|
||||
let handlers = vnode.data && vnode.data.on || vnode.componentOptions && vnode.componentOptions.listeners;
|
||||
|
||||
if (handlers && handlers[emitName]) {
|
||||
handlers[emitName].fns(data);
|
||||
}
|
||||
};
|
||||
63
website/client/directives/dragdrop.directive.js
Normal file
63
website/client/directives/dragdrop.directive.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import {emit} from './directive.common';
|
||||
|
||||
import _keys from 'lodash/keys';
|
||||
import _without from 'lodash/without';
|
||||
|
||||
/**
|
||||
* DRAG_GROUP is a static custom value
|
||||
* KEY_OF_ITEM
|
||||
*
|
||||
* v-drag.DRAG_GROUP="KEY_OF_ITEM"
|
||||
* v-drag.drop.DRAG_GROUP="KEY_OF_ITEM" @dropped="callback" @dragover="optional"
|
||||
*/
|
||||
|
||||
const DROPPED_EVENT_NAME = 'dropped';
|
||||
const DRAGOVER_EVENT_NAME = 'dragover';
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
el.isDropHandler = binding.modifiers.drop === true;
|
||||
el.dragGroup = _without(_keys(binding.modifiers), 'drop')[0];
|
||||
el.key = binding.value;
|
||||
|
||||
if (!el.isDropHandler) {
|
||||
el.draggable = true;
|
||||
el.handleDrag = (ev) => {
|
||||
ev.dataTransfer.setData('KEY', binding.value);
|
||||
};
|
||||
el.addEventListener('dragstart', el.handleDrag);
|
||||
} else {
|
||||
el.handleDragOver = (ev) => {
|
||||
let dragOverEventData = {
|
||||
dropable: true,
|
||||
draggingKey: ev.dataTransfer.getData('KEY'),
|
||||
};
|
||||
|
||||
emit(vnode, DRAGOVER_EVENT_NAME, dragOverEventData);
|
||||
|
||||
if (dragOverEventData.dropable) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
el.handleDrop = (ev) => {
|
||||
let dropEventData = {
|
||||
draggingKey: ev.dataTransfer.getData('KEY'),
|
||||
};
|
||||
|
||||
emit(vnode, DROPPED_EVENT_NAME, dropEventData);
|
||||
};
|
||||
|
||||
el.addEventListener('dragover', el.handleDragOver);
|
||||
el.addEventListener('drop', el.handleDrop);
|
||||
}
|
||||
},
|
||||
|
||||
unbind (el) {
|
||||
if (!el.isDropHandler) {
|
||||
el.removeEventListener('drag', el.handleDrag);
|
||||
} else {
|
||||
el.removeEventListener('dragover', el.handleDragOver);
|
||||
el.removeEventListener('drop', el.handleDrop);
|
||||
}
|
||||
},
|
||||
};
|
||||
31
website/client/directives/resize.directive.js
Normal file
31
website/client/directives/resize.directive.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import _throttle from 'lodash/throttle';
|
||||
|
||||
import { emit } from './directive.common';
|
||||
|
||||
/**
|
||||
* v-resize="throttleTimeout", @resized="callback()"
|
||||
*/
|
||||
|
||||
const EVENT_NAME = 'resized';
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
el.handleWindowResize = _throttle(() => {
|
||||
emit(vnode, EVENT_NAME, {
|
||||
width: el.clientWidth,
|
||||
height: el.clientHeight,
|
||||
});
|
||||
}, binding.value);
|
||||
|
||||
window.addEventListener('resize', el.handleWindowResize);
|
||||
|
||||
// send the first width
|
||||
Vue.nextTick(el.handleWindowResize);
|
||||
},
|
||||
|
||||
unbind (el) {
|
||||
window.removeEventListener('resize', el.handleWindowResize);
|
||||
},
|
||||
};
|
||||
@@ -19,7 +19,7 @@ import UserTasks from './components/userTasks';
|
||||
const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'./components/inventory/index');
|
||||
const ItemsPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/items/index');
|
||||
const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/equipment/index');
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/stable');
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/stable/index');
|
||||
|
||||
// Social
|
||||
const InboxPage = () => import(/* webpackChunkName: "inbox" */ './components/social/inbox/index');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import equipOp from 'common/script/ops/equip';
|
||||
import hatchOp from 'common/script/ops/hatch';
|
||||
import feedOp from 'common/script/ops/feed';
|
||||
|
||||
export function equip (store, params) {
|
||||
const user = store.state.user.data;
|
||||
@@ -9,4 +11,24 @@ export function equip (store, params) {
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
}
|
||||
|
||||
export function hatch (store, params) {
|
||||
const user = store.state.user.data;
|
||||
hatchOp(user, {params});
|
||||
axios
|
||||
.post(`/api/v3/user/hatch/${params.egg}/${params.hatchingPotion}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
export function feed (store, params) {
|
||||
const user = store.state.user.data;
|
||||
feedOp(user, {params});
|
||||
axios
|
||||
.post(`/api/v3/user/feed/${params.pet}/${params.food}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"viewParty": "View Party",
|
||||
"shops": "Shops",
|
||||
"faq": "FAQ",
|
||||
"costumePopoverText": "Select \"Use Costume\" to equip items to your avatar without affecting the stats from your Battle Gear! This means that you can dress up your avatar in whatever outfit you like while still having your best Battle Gear equipped.",
|
||||
@@ -25,6 +24,14 @@
|
||||
"groupBy2": "Group By",
|
||||
"quantity": "Quantity",
|
||||
"AZ": "A-Z",
|
||||
"costumeDisabled": "You have disabled your costume.",
|
||||
"filterByStandard": "Standard",
|
||||
"filterByMagicPotion": "Magin Potion",
|
||||
"filterByQuest": "Quest",
|
||||
"standard": "Standard",
|
||||
"sortByColor": "Color",
|
||||
"sortByHatchable": "Hatchable",
|
||||
"haveHatchablePet": "You have a <%= potion %> and <%= egg %> to hatch this pet! <b>Click and hold</b> the paw print to hatch.",
|
||||
"editAvatar": "Edit Avatar",
|
||||
"sort": "Sort",
|
||||
"memberCount": "Member Count",
|
||||
@@ -69,5 +76,12 @@
|
||||
"sendMessage": "Send Message",
|
||||
"removeManager2": "Remove Manager",
|
||||
"promoteToLeader": "Promote to Leader",
|
||||
"inviteFriendsParty": "Inviting friends to your party will grant you an exclusive <br/> Quest Scroll to battle the Basi-List together!"
|
||||
"inviteFriendsParty": "Inviting friends to your party will grant you an exclusive <br/> Quest Scroll to battle the Basi-List together!",
|
||||
"showAllAnimals": "Show All <%= color %> <%= type %>",
|
||||
"showLessAnimals": "Show Less <%= color %> <%= type %>",
|
||||
"quickInventory": "Quick Inventory",
|
||||
"noFoodAvailable": "You don't have any food.",
|
||||
"gotIt": "Got it!",
|
||||
"welcomeStable": "Welcome to the Stable!",
|
||||
"welcomeStableText": "I'm Matt, the Beast Master. Starting at level 3, you can hatch Pets from Eggs by using Potions you find! When you hatch a Pet from your Inventory, it will appear here! Click a Pet's to add it to your avatar. Feed them with the Food you find in your Inventory after level 3, and they'll grow into hardy Mounts."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user