Initial item filters + restyling

pull/12/head
Matheus Clemente 2024-04-04 18:13:37 -03:00
parent dc08ae7ea5
commit ea35cbcadf
3 changed files with 245 additions and 196 deletions

View File

@ -1,4 +1,5 @@
@color_1: #bbb; @color_1: #bbb;
@line_size: 48px;
#compendium { #compendium {
.directory-footer { .directory-footer {
@ -117,6 +118,10 @@
.small-select { .small-select {
width: 40px; width: 40px;
} }
.direction {
flex: 0;
padding: 2px;
}
} }
} }
footer { footer {
@ -182,6 +187,107 @@
} }
} }
} }
.item-browser {
li {
.item-image {
max-width: @line_size;
height: @line_size;
}
.item-name {
padding-left: 5px;
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
}
}
.feat-browser {
li {
cursor: default;
vertical-align: middle;
margin: 2px 0;
height: @line_size;
.item-image {
max-width: @line_size;
height: @line_size;
}
.item-name {
padding-left: 5px;
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
}
}
.spell-browser {
li {
cursor: default;
vertical-align: middle;
margin: 2px 0;
height: @line_size;
.item-image {
max-width: @line_size;
height: @line_size;
}
.item-name {
padding-left: 5px;
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: @line_size;
}
}
.spell {
.spell-level {
text-align: center;
font-weight: 900;
max-width: 18px;
height: @line_size;
}
.spell-tags {
text-align: right;
margin-right: 3px;
font-weight: 900;
max-width: 100px;
height: @line_size;
}
}
}
.browser { .browser {
height: 100%; height: 100%;
overflow-y: hidden !important; overflow-y: hidden !important;
@ -196,117 +302,19 @@
display: inline-block; display: inline-block;
min-width: 15px; min-width: 15px;
} }
}
.item-browser { .list-area {
li { li {
cursor: default; cursor: default;
vertical-align: middle; vertical-align: middle;
line-height: 16px; // margin: 4px 0;
margin: 2px 0; height: @line_size;
height: 32px; border-bottom: 1px solid var(--color-border-dark-3);
.item-image { &:first-child {
max-width: 32px; border-top: none;
height: 32px;
} }
.item-name {
padding-left: 5px; &:hover {
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
}
}
.feat-browser {
li {
cursor: default;
vertical-align: middle;
line-height: 16px;
margin: 2px 0;
height: 32px;
.item-image {
max-width: 32px;
height: 32px;
}
.item-name {
padding-left: 5px;
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
}
}
.spell-browser {
li {
cursor: default;
vertical-align: middle;
line-height: 16px;
margin: 2px 0;
height: 32px;
.item-image {
max-width: 32px;
height: 32px;
}
.item-name {
padding-left: 5px;
flex: 2;
}
.feat-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
.item-tags {
text-align: right;
margin-right: 3px;
margin-left: 3px;
text-transform: capitalize;
height: 32px;
}
}
.spell {
.spell-level {
text-align: center;
font-weight: 900;
max-width: 18px;
height: 32px;
}
.spell-tags {
text-align: right;
margin-right: 3px;
font-weight: 900;
max-width: 100px;
height: 32px;
}
}
}
.browser {
li:hover {
.line { .line {
h4 { h4 {
text-shadow: 0 0 8px var(--color-shadow-primary); text-shadow: 0 0 8px var(--color-shadow-primary);
@ -314,21 +322,22 @@
} }
} }
.image {
max-width: 32px;
height: 32px;
img { img {
width: 32px; flex: 0 0 @line_size;
height: 32px; width: @line_size;
height: @line_size;
object-fit: cover;
object-position: 50% 0;
border: none; border: none;
object-fit: contain;
}
} }
.line { .line {
line-height: 25px;
margin: auto 0 auto 8px; margin: auto 0 auto 8px;
.name {
margin: 0;
}
.tags { .tags {
.bold { .bold {
font-weight: bold; font-weight: bold;
@ -339,41 +348,27 @@
} }
} }
}
}
.rarity {
text-transform: capitalize;
}
.item { .item {
cursor: default;
vertical-align: middle;
line-height: 16px;
margin: 2px 0;
height: 32px;
.feat-tags { .feat-tags {
text-align: right; text-align: right;
margin-right: 3px; margin-right: 3px;
margin-left: 3px; margin-left: 3px;
text-transform: capitalize; text-transform: capitalize;
height: 32px; height: @line_size;
} }
.item-tags { .item-tags {
text-align: right; text-align: right;
margin-right: 3px; margin-right: 3px;
margin-left: 3px; margin-left: 3px;
text-transform: capitalize; text-transform: capitalize;
height: 32px; height: @line_size;
}
}
.actor {
cursor: default;
vertical-align: middle;
line-height: 64px;
margin: 4px 0;
.image {
max-width: 64px;
height: 64px;
img {
width: 64px;
height: 64px;
}
} }
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="npc-browser browser flexrow"> <div class="actor-browser browser flexrow">
<section class="control-area flexcol"> <section class="control-area flexcol">
<div class="controls"> <div class="controls">
<div class="filtercontainer"> <div class="filtercontainer">
@ -12,9 +12,13 @@
<div class="form-group"> <div class="form-group">
<label>{{ game.i18n.localize('Sort by:') }}</label> <label>{{ game.i18n.localize('Sort by:') }}</label>
<div class="form-fields"> <div class="form-fields">
<select class="sort" name="sortorder" v-model="sortBy"> <select class="sort" v-model="sortBy">
<option v-for="(option, index) in sortOptions" :key="index" :value="option.value">{{ option.label }}</option> <option v-for="(option, index) in sortOptions" :key="index" :value="option.value">{{ option.label }}</option>
</select> </select>
<a class="direction" data-direction="asc" @click="changeDirection()">
<i class="fa-solid fa-sort-numeric-up" v-if="direction === 'asc'"></i>
<i class="fa-solid fa-sort-numeric-down-alt" v-if="direction !== 'asc'"></i>
</a>
</div> </div>
</div> </div>
</div> </div>
@ -149,16 +153,13 @@
<li v-for="(entry, entryKey) in entries" :key="entryKey" :class="`flexrow draggable compendium-browser-row${entryKey >= pager.lastIndex - 1 && entryKey < pager.totalRows - 1 ? ' compendium-browser-row-observe': ''} document actor`" :data-document-id="entry._id" @click="openDocument(entry.uuid)" @dragstart="startDrag($event, entry, 'Actor')" draggable="true"> <li v-for="(entry, entryKey) in entries" :key="entryKey" :class="`flexrow draggable compendium-browser-row${entryKey >= pager.lastIndex - 1 && entryKey < pager.totalRows - 1 ? ' compendium-browser-row-observe': ''} document actor`" :data-document-id="entry._id" @click="openDocument(entry.uuid)" @dragstart="startDrag($event, entry, 'Actor')" 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. -->
<div class="image">
<img :src="entry.img ?? 'icons/svg/mystery-man.svg'"/> <img :src="entry.img ?? 'icons/svg/mystery-man.svg'"/>
</div>
<div class="line"> <div class="line">
<!-- First row is the title. --> <!-- First row is the title. -->
<h4 class="name">[{{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}] {{ entry.name }}</h4> <h4 class="name">[{{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}] {{ entry.name }}</h4>
<!-- Second row is supplemental info. --> <!-- Second row is supplemental info. -->
<div class="tags flexrow"> <div class="tags flexrow">
<div class="numbers flexrow"> <div class="flexrow">
<!-- <span class="cr" :data-tooltip="game.i18n.localize('Challenge rating')">CR {{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}</span> -->
<span class="hp"><span class="bold">HP:</span> {{ entry.system.attributes.hp.max }}</span> <span class="hp"><span class="bold">HP:</span> {{ entry.system.attributes.hp.max }}</span>
<span class="ac"><span class="bold">AC:</span> {{ entry.system.attributes.ac.flat }}</span> <span class="ac"><span class="bold">AC:</span> {{ entry.system.attributes.ac.flat }}</span>
</div> </div>
@ -226,6 +227,7 @@ export default {
}, },
// Sorting. // Sorting.
sortBy: 'name', sortBy: 'name',
direction: 'asc',
sortOptions: [ sortOptions: [
{ value: 'name', label: game.i18n.localize('Name') }, { value: 'name', label: game.i18n.localize('Name') },
{ value: 'cr', label: game.i18n.localize('Challenge Rating') }, { value: 'cr', label: game.i18n.localize('Challenge Rating') },
@ -278,6 +280,7 @@ export default {
*/ */
resetFilters() { resetFilters() {
this.sortBy = 'name'; this.sortBy = 'name';
this.direction = 'asc';
this.name = ''; this.name = '';
this.crRange = [0, 30]; this.crRange = [0, 30];
this.legact = ''; this.legact = '';
@ -289,13 +292,17 @@ export default {
this.size = []; this.size = [];
this.creatureType = []; this.creatureType = [];
}, },
changeDirection() {
if (this.direction === "asc") this.direction = "desc";
else this.direction = "asc";
},
/** /**
* Get multiselect options. * Get multiselect options.
*/ */
getOptions(config) { getOptions(config) {
const options = {}; const options = {};
for (let [key, value] of Object.entries(config)) { for (let [key, value] of Object.entries(config)) {
options[key] = value.label; options[key] = value.label ?? value;
} }
return options; return options;
}, },
@ -372,8 +379,7 @@ export default {
if (this.pager.lastIndex == 0) { if (this.pager.lastIndex == 0) {
this.pager.lastIndex = this.pager.perPage - 1; this.pager.lastIndex = this.pager.perPage - 1;
} }
} } else {
else {
this.pager.totalRows = 0; this.pager.totalRows = 0;
} }
@ -381,14 +387,16 @@ export default {
result = result.sort((a, b) => { result = result.sort((a, b) => {
// Add sorts here. // Add sorts here.
switch (this.sortBy) { switch (this.sortBy) {
case 'name': case 'cr':
return a.name.localeCompare(b.name); return a.system.details.cr - b.system.details.cr;
case 'size': case 'size':
return a.system.traits.size.localeCompare(b.system.traits.size); return a.system.traits.size.localeCompare(b.system.traits.size);
} }
// If no sorts match, sort by CR. return a.name.localeCompare(b.name);
return a.system.details.cr - b.system.details.cr;
}); });
if (this.direction === "desc") {
result = result.reverse();
}
// Return results. // Return results.
return this.pager.totalRows > 0 return this.pager.totalRows > 0

View File

@ -19,6 +19,33 @@
</div> </div>
</div> </div>
</div> </div>
<div class="filtercontainer">
<h3>{{ game.i18n.localize('General') }}</h3>
<div class="filters">
<div class="filter">
<label class="unit-title" for="compendiumBrowser.type">{{ game.i18n.localize('Type') }}</label>
<Multiselect
v-model="type"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(itemTypes)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.rarity">{{ game.i18n.localize('Rarity') }}</label>
<Multiselect
v-model="rarity"
mode="tags"
:class="{ 'rarity': true }"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.itemRarity)"
/>
</div>
</div>
</div>
</div> </div>
<footer> <footer>
<!-- Reset. --> <!-- Reset. -->
@ -33,28 +60,19 @@
<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, entry, 'Actor')" 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. -->
<div class="image">
<img :src="equipment.img" /> <img :src="equipment.img" />
</div>
<div class="line"> <div class="line">
<!-- 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="grid equipment-grid"> <div class="tags flexrow">
<div class="equipment-bonus flexrow" :data-tooltip="game.i18n.localize('ARCHMAGE.bonuses')" data-tooltip-direction="RIGHT" v-if="equipment.system.attributes"> <div v-if="equipment.system.properties.length">
<span class="bonus" v-for="(bonus, bonusProp) in getBonuses(equipment)" :key="bonusProp"> <span v-for="(prop, index) of equipment.system.properties" :key="prop">
<span class="bonus-label">{{localizeEquipmentBonus(bonusProp)}} </span> {{ prop }}<span v-if="index != Object.keys(equipment.system.properties).length - 1">, </span>
<span class="bonus-value">{{numberFormat(bonus, 0, true)}}</span>
</span> </span>
</div> </div>
<div class="equipment-usage" v-if="equipment.system?.powerUsage?.value" :data-tooltip="game.i18n.localize('ARCHMAGE.GROUPS.powerUsage')"> <div v-if="equipment.system.rarity && equipment.system.rarity !== 'common'">
{{ '' }} <span class="rarity">{{ CONFIG.DND5E.itemRarity[equipment.system.rarity] }}</span>
</div>
<div class="equipment-chakra" :data-tooltip="game.i18n.localize('ARCHMAGE.chakra')" v-if="equipment.system.chackra">
{{localize(`ARCHMAGE.CHAKRA.${equipment.system.chackra}Label`)}}
</div>
<div class="equipment-recharge" :data-tooltip="game.i18n.localize('ARCHMAGE.recharge')">
{{ `${equipment.system?.recharge?.value > 0 ? Number(equipment.system.recharge.value) + '+' : ''}`}}
</div> </div>
</div> </div>
</div> </div>
@ -125,6 +143,8 @@ export default {
packIndex: [], packIndex: [],
// Filters. // Filters.
name: '', name: '',
type: [],
rarity: [],
chakra: [], chakra: [],
recharge: [], recharge: [],
bonuses: [], bonuses: [],
@ -161,6 +181,8 @@ export default {
resetFilters() { resetFilters() {
this.sortBy = 'name'; this.sortBy = 'name';
this.name = ''; this.name = '';
this.type = [];
this.rarity = [];
this.chakra = []; this.chakra = [];
this.recharge = []; this.recharge = [];
this.bonuses = []; this.bonuses = [];
@ -182,8 +204,25 @@ export default {
} }
return bonuses; return bonuses;
}, },
getOptions(config) {
const options = {};
for (let [key, value] of Object.entries(config)) {
options[key] = value.label ?? value;
}
return options;
}
}, },
computed: { computed: {
itemTypes() {
return {
consumable: game.i18n.localize("ITEM.TypeConsumable"),
container: game.i18n.localize("ITEM.TypeContainer"),
equipment: game.i18n.localize("ITEM.TypeEquipment"),
loot: game.i18n.localize("ITEM.TypeLoot"),
tool: game.i18n.localize("ITEM.TypeTool"),
weapon: game.i18n.localize("ITEM.TypeWeapon"),
};
},
bonusOptions() { bonusOptions() {
return [ return [
{ {
@ -283,6 +322,13 @@ export default {
result = result.filter(entry => entry.name.toLocaleLowerCase().includes(name)); result = result.filter(entry => entry.name.toLocaleLowerCase().includes(name));
} }
if (Array.isArray(this.type) && this.type.length > 0) {
result = result.filter(entry => this.type.includes(entry.type));
}
if (Array.isArray(this.rarity) && this.rarity.length > 0) {
result = result.filter(entry => this.rarity.includes(entry.system.rarity));
}
// Handle multiselect filters, which use arrays as their values. // Handle multiselect filters, which use arrays as their values.
if (Array.isArray(this.chakra) && this.chakra.length > 0) { if (Array.isArray(this.chakra) && this.chakra.length > 0) {
// @todo chakra is misspelled in our data model. We need to fix that :( // @todo chakra is misspelled in our data model. We need to fix that :(
@ -339,12 +385,10 @@ export default {
// Sort. // Sort.
result = result.sort((a, b) => { result = result.sort((a, b) => {
return a.name.localeCompare(b.name); // Add sorts here.
});
// Sort.
result = result.sort((a, b) => {
switch (this.sortBy) { switch (this.sortBy) {
case 'type':
return a.type.localeCompare(b.type);
case 'chakra': case 'chakra':
return (a.system?.chackra ?? '').localeCompare((b.system?.chackra ?? '')); return (a.system?.chackra ?? '').localeCompare((b.system?.chackra ?? ''));
case 'usage': case 'usage':
@ -372,12 +416,14 @@ export default {
], [ ], [
'system.activation.type', 'system.activation.type',
'system.armor.type', 'system.armor.type',
'system.container',
'system.damage', 'system.damage',
'system.properties',
'system.rarity', 'system.rarity',
'system.source.book', 'system.source.book',
'system.type' 'system.type'
]).then(packIndex => { ]).then(packIndex => {
this.packIndex = packIndex; this.packIndex = packIndex.filter((e) => !e.system.container);
this.loaded = true; this.loaded = true;
}); });