Item filters + restyle

pull/12/head
Matheus Clemente 2024-04-04 23:20:24 -03:00
parent c218a2001e
commit 82277afd66
3 changed files with 124 additions and 110 deletions

View File

@ -307,12 +307,13 @@
li { li {
cursor: default; cursor: default;
vertical-align: middle; vertical-align: middle;
// margin: 4px 0;
height: @line_size; height: @line_size;
border-bottom: 1px solid var(--color-border-dark-3);
&:first-child { &:first-child {
border-top: none; border-top: none;
} }
&:nth-child(odd) {
background-color: rgba(0, 0, 0, 0.12);
}
&:hover { &:hover {
.line { .line {

View File

@ -3,22 +3,7 @@
<section class="control-area flexcol"> <section class="control-area flexcol">
<div class="controls"> <div class="controls">
<div class="filtercontainer"> <FilterNameSort v-model="name" :filters="sorts"/>
<!-- Name filter. -->
<div class="filter">
<input type="text" name="compendiumBrowser.name" v-model="name" :placeholder="game.i18n.localize('Name')" />
</div>
<!-- Sort -->
<div class="form-group">
<label>{{ game.i18n.localize('Sort by:') }}</label>
<div class="form-fields">
<select class="sort" name="sortorder" v-model="sortBy">
<option v-for="(option, index) in sortOptions" :key="index" :value="option.value">{{ option.label }}</option>
</select>
</div>
</div>
</div>
<div class="filtercontainer"> <div class="filtercontainer">
<h3>{{ game.i18n.localize('General') }}</h3> <h3>{{ game.i18n.localize('General') }}</h3>
@ -44,6 +29,66 @@
:options="getOptions(CONFIG.DND5E.itemRarity)" :options="getOptions(CONFIG.DND5E.itemRarity)"
/> />
</div> </div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.activation">{{ game.i18n.localize('Activation') }}</label>
<Multiselect
v-model="activation"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.abilityActivationTypes)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.damageType">{{ game.i18n.localize('Damage Type') }}</label>
<Multiselect
v-model="damageType"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.damageTypes)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.uses">{{ game.i18n.localize('Has Limited Uses') }}</label>
<input type="checkbox" v-model="uses">
</div>
</div>
</div>
<div class="filtercontainer">
<h3>{{ game.i18n.localize('Subtypes') }}</h3>
<div class="filters" style="display: none;">
<div class="filter">
<label class="unit-title" for="compendiumBrowser.weaponTypes">{{ game.i18n.localize('Weapon Types') }}</label>
<Multiselect
v-model="weaponTypes"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.weaponTypes)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.equipmentTypes">{{ game.i18n.localize('Equipment Types') }}</label>
<Multiselect
v-model="equipmentTypes"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.equipmentTypes)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.consumableTypes">{{ game.i18n.localize('Consumable Types') }}</label>
<Multiselect
v-model="consumableTypes"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.consumableTypes)"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -57,7 +102,7 @@
<!-- Items results. --> <!-- Items results. -->
<ul v-if="loaded" class="compendium-browser-results compendium-browser-items"> <ul v-if="loaded" class="compendium-browser-results compendium-browser-items">
<!-- Individual items entries. --> <!-- Individual items entries. -->
<li v-for="(equipment, equipmentKey) in entries" :key="equipmentKey" :class="`flexrow draggable compendium-browser-row${equipmentKey >= pager.lastIndex - 1 && equipmentKey < pager.totalRows - 1 ? ' compendium-browser-row-observe': ''} document item`" :data-document-id="equipment._id" @click="openDocument(equipment.uuid, 'Item')" @dragstart="startDrag($event, entry, 'Actor')" draggable="true"> <li v-for="(equipment, equipmentKey) in entries" :key="equipmentKey" :class="`flexrow draggable compendium-browser-row${equipmentKey >= pager.lastIndex - 1 && equipmentKey < pager.totalRows - 1 ? ' compendium-browser-row-observe': ''} document item`" :data-document-id="equipment._id" @click="openDocument(equipment.uuid, 'Item')" @dragstart="startDrag($event, equipment, 'Item')" draggable="true">
<!-- Both the image and title have drag events. These are primarily separated so that --> <!-- Both the image and title have drag events. These are primarily separated so that -->
<!-- if a user drags the token, it will only show the token as their drag preview. --> <!-- if a user drags the token, it will only show the token as their drag preview. -->
<img :src="equipment.img" /> <img :src="equipment.img" />
@ -65,15 +110,13 @@
<!-- First row is the title. --> <!-- First row is the title. -->
<h4 class="name">{{ equipment.name }}</h4> <h4 class="name">{{ equipment.name }}</h4>
<!-- Second row is supplemental info. --> <!-- Second row is supplemental info. -->
<div class="tags flexrow"> <div class="tags">
<div v-if="equipment.system.properties.length"> <span v-if="equipment.system.rarity" class="rarity">
<span v-for="(prop, index) of equipment.system.properties" :key="prop"> {{ CONFIG.DND5E.itemRarity[equipment.system.rarity] }}<span v-if="equipment.system.properties.length">, </span>
{{ prop }}<span v-if="index != Object.keys(equipment.system.properties).length - 1">, </span> </span>
</span> <span v-if="equipment.system.properties.length" v-for="(prop, index) of equipment.system.properties" :key="prop">
</div> {{ CONFIG.DND5E.itemProperties[prop].label }}<span v-if="index != Object.keys(equipment.system.properties).length - 1">, </span>
<div v-if="equipment.system.rarity && equipment.system.rarity !== 'common'"> </span>
<span class="rarity">{{ CONFIG.DND5E.itemRarity[equipment.system.rarity] }}</span>
</div>
</div> </div>
</div> </div>
</li> </li>
@ -89,6 +132,7 @@ import { onUpdated } from 'vue';
// External components. // External components.
import Slider from '@vueform/slider'; import Slider from '@vueform/slider';
import Multiselect from '@vueform/multiselect'; import Multiselect from '@vueform/multiselect';
import FilterNameSort from '@/components/dialogs/compendium-browser/filters/FilterNameSort.vue';
// Helper methods. // Helper methods.
import { import {
getPackIndex, getPackIndex,
@ -105,7 +149,8 @@ export default {
// Imported components that need to be available in the <template> // Imported components that need to be available in the <template>
components: { components: {
Slider, Slider,
Multiselect Multiselect,
FilterNameSort
}, },
setup() { setup() {
return { return {
@ -133,22 +178,27 @@ export default {
lastIndex: 50, lastIndex: 50,
totalRows: 0, totalRows: 0,
}, },
sorts: {
sortBy: 'name',
direction: 'asc',
sortOptions: [
{ value: 'name', label: game.i18n.localize('Name') },
// { value: 'type', label: game.i18n.localize('Type') },
],
},
// Sorting. // Sorting.
sortBy: 'name',
sortOptions: [
{ value: 'name', label: game.i18n.localize('Name') },
{ value: 'type', label: game.i18n.localize('Type') },
],
// Our list of pseudo documents returned from the compendium. // Our list of pseudo documents returned from the compendium.
packIndex: [], packIndex: [],
// Filters. // Filters.
name: '', name: '',
type: [], type: [],
rarity: [], rarity: [],
chakra: [], activation: [],
recharge: [], damageType: [],
bonuses: [], uses: false,
powerUsage: [], weaponTypes: [],
consumableTypes: [],
equipmentTypes: [],
} }
}, },
methods: { methods: {
@ -179,30 +229,17 @@ export default {
* Click event to reset our filters. * Click event to reset our filters.
*/ */
resetFilters() { resetFilters() {
this.sortBy = 'name'; this.sorts.sortBy = 'name';
this.sorts.direction = 'asc';
this.name = ''; this.name = '';
this.type = []; this.type = [];
this.rarity = []; this.rarity = [];
this.chakra = []; this.activation = [];
this.recharge = []; this.damageType = [];
this.bonuses = []; this.uses = false;
this.powerUsage = []; this.weaponTypes = [];
}, this.consumableTypes = [];
getBonuses(equipment) { this.equipmentTypes = [];
let bonuses = {};
for (let [prop, value] of Object.entries(equipment.system.attributes)) {
if (value.bonus) {
bonuses[prop] = value.bonus
}
else if (prop == 'attack') {
for (let [atkProp, atkValue] of Object.entries(value)) {
if (atkValue.bonus) {
bonuses[atkProp] = atkValue.bonus;
}
}
}
}
return bonuses;
}, },
getOptions(config) { getOptions(config) {
const options = {}; const options = {};
@ -328,48 +365,27 @@ export default {
if (Array.isArray(this.rarity) && this.rarity.length > 0) { if (Array.isArray(this.rarity) && this.rarity.length > 0) {
result = result.filter(entry => this.rarity.includes(entry.system.rarity)); result = result.filter(entry => this.rarity.includes(entry.system.rarity));
} }
if (Array.isArray(this.activation) && this.activation.length > 0) {
// Handle multiselect filters, which use arrays as their values. result = result.filter(entry => entry.system.activation && this.activation.includes(entry.system.activation.type))
if (Array.isArray(this.chakra) && this.chakra.length > 0) {
// @todo chakra is misspelled in our data model. We need to fix that :(
result = result.filter(entry => this.chakra.includes(entry.system?.chackra));
} }
if (Array.isArray(this.powerUsage) && this.powerUsage.length > 0) { if (Array.isArray(this.damageType) && this.damageType.length > 0) {
result = result.filter(entry => this.powerUsage.includes(entry.system?.powerUsage?.value ?? 'other'));
}
// Recharge.
if (Array.isArray(this.recharge) && this.recharge.length > 0) {
result = result.filter(entry => { result = result.filter(entry => {
let allowEntry = false; if (!entry.system.damage) return false;
for (let rechargeOption of this.rechargeOptions) { const damageTypes = entry.system.damage.parts.map((d) => d[1]);
if (this.recharge.includes(rechargeOption.value)) { return this.damageType.some((d) => damageTypes.includes(d));
const rechargeEntry = parseInt(entry.system?.recharge?.value ?? 0); })
if (rechargeEntry >= rechargeOption.value && rechargeEntry <= rechargeOption.next) {
allowEntry = true;
break;
}
}
}
return allowEntry;
});
} }
if (this.uses) {
// Bonus options. result = result.filter(entry => entry.system.uses && entry.system.uses.max);
if (Array.isArray(this.bonuses) && this.bonuses.length > 0) { }
result = result.filter(entry => { if (Array.isArray(this.weaponTypes) && this.weaponTypes.length > 0) {
let allowEntry = false; result = result.filter(entry => entry.system.type && this.weaponTypes.includes(entry.system.type.value));
for (let bonusOption of this.bonusOptions) { }
if (this.bonuses.includes(bonusOption.value)) { if (Array.isArray(this.consumableTypes) && this.consumableTypes.length > 0) {
const prop = this.foundry.utils.getProperty(entry, bonusOption.dataProp); result = result.filter(entry => entry.system.type && this.consumableTypes.includes(entry.system.type.value));
if (Number.isNumeric(prop) && prop !== 0) { }
allowEntry = true; if (Array.isArray(this.equipmentTypes) && this.equipmentTypes.length > 0) {
break; result = result.filter(entry => entry.system.type && this.equipmentTypes.includes(entry.system.type.value));
}
}
}
return allowEntry;
});
} }
// Reflow pager. // Reflow pager.
@ -386,18 +402,13 @@ export default {
// Sort. // Sort.
result = result.sort((a, b) => { result = result.sort((a, b) => {
// Add sorts here. // Add sorts here.
switch (this.sortBy) { switch (this.sorts.sortBy) {
case 'type':
return a.type.localeCompare(b.type);
case 'chakra':
return (a.system?.chackra ?? '').localeCompare((b.system?.chackra ?? ''));
case 'usage':
return (a.system?.powerUsage?.value ?? '').localeCompare((b.system?.powerUsage?.value ?? ''));
case 'recharge':
return (a.system?.recharge?.value ?? 0) - (b.system?.recharge?.value ?? 0);
} }
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
if (this.sorts.direction === "desc") {
result = result.reverse();
}
// Return results. // Return results.
return this.pager.totalRows > 0 return this.pager.totalRows > 0
@ -415,13 +426,13 @@ export default {
'dnd5e.items', 'dnd5e.items',
], [ ], [
'system.activation.type', 'system.activation.type',
'system.armor.type',
'system.container', 'system.container',
'system.damage', 'system.damage',
'system.properties', 'system.properties',
'system.rarity', 'system.rarity',
'system.source.book', 'system.source.book',
'system.type' 'system.type',
'system.uses',
]).then(packIndex => { ]).then(packIndex => {
this.packIndex = packIndex.filter((e) => !e.system.container); this.packIndex = packIndex.filter((e) => !e.system.container);
this.loaded = true; this.loaded = true;

View File

@ -38,7 +38,9 @@ export default {
game game
} }
}, },
data() {}, data() {
return {};
},
methods: { methods: {
// updateValue(event) { // updateValue(event) {
// this.valueMutable = event.target.value; // this.valueMutable = event.target.value;