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

View File

@ -1,5 +1,5 @@
<template>
<div class="npc-browser browser flexrow">
<div class="actor-browser browser flexrow">
<section class="control-area flexcol">
<div class="controls">
<div class="filtercontainer">
@ -12,9 +12,13 @@
<div class="form-group">
<label>{{ game.i18n.localize('Sort by:') }}</label>
<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>
</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>
@ -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">
<!-- 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. -->
<div class="image">
<img :src="entry.img ?? 'icons/svg/mystery-man.svg'"/>
</div>
<div class="line">
<!-- First row is the title. -->
<h4 class="name">[{{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}] {{ entry.name }}</h4>
<!-- Second row is supplemental info. -->
<div class="tags flexrow">
<div class="numbers flexrow">
<!-- <span class="cr" :data-tooltip="game.i18n.localize('Challenge rating')">CR {{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}</span> -->
<div class="flexrow">
<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>
</div>
@ -226,6 +227,7 @@ export default {
},
// Sorting.
sortBy: 'name',
direction: 'asc',
sortOptions: [
{ value: 'name', label: game.i18n.localize('Name') },
{ value: 'cr', label: game.i18n.localize('Challenge Rating') },
@ -278,6 +280,7 @@ export default {
*/
resetFilters() {
this.sortBy = 'name';
this.direction = 'asc';
this.name = '';
this.crRange = [0, 30];
this.legact = '';
@ -289,13 +292,17 @@ export default {
this.size = [];
this.creatureType = [];
},
changeDirection() {
if (this.direction === "asc") this.direction = "desc";
else this.direction = "asc";
},
/**
* Get multiselect options.
*/
getOptions(config) {
const options = {};
for (let [key, value] of Object.entries(config)) {
options[key] = value.label;
options[key] = value.label ?? value;
}
return options;
},
@ -372,8 +379,7 @@ export default {
if (this.pager.lastIndex == 0) {
this.pager.lastIndex = this.pager.perPage - 1;
}
}
else {
} else {
this.pager.totalRows = 0;
}
@ -381,14 +387,16 @@ export default {
result = result.sort((a, b) => {
// Add sorts here.
switch (this.sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'cr':
return a.system.details.cr - b.system.details.cr;
case 'size':
return a.system.traits.size.localeCompare(b.system.traits.size);
}
// If no sorts match, sort by CR.
return a.system.details.cr - b.system.details.cr;
return a.name.localeCompare(b.name);
});
if (this.direction === "desc") {
result = result.reverse();
}
// Return results.
return this.pager.totalRows > 0

View File

@ -19,6 +19,33 @@
</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>
<footer>
<!-- 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">
<!-- 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. -->
<div class="image">
<img :src="equipment.img" />
</div>
<div class="line">
<!-- First row is the title. -->
<h4 class="name">{{ equipment.name }}</h4>
<!-- Second row is supplemental info. -->
<div class="grid equipment-grid">
<div class="equipment-bonus flexrow" :data-tooltip="game.i18n.localize('ARCHMAGE.bonuses')" data-tooltip-direction="RIGHT" v-if="equipment.system.attributes">
<span class="bonus" v-for="(bonus, bonusProp) in getBonuses(equipment)" :key="bonusProp">
<span class="bonus-label">{{localizeEquipmentBonus(bonusProp)}} </span>
<span class="bonus-value">{{numberFormat(bonus, 0, true)}}</span>
<div class="tags flexrow">
<div v-if="equipment.system.properties.length">
<span v-for="(prop, index) of equipment.system.properties" :key="prop">
{{ prop }}<span v-if="index != Object.keys(equipment.system.properties).length - 1">, </span>
</span>
</div>
<div class="equipment-usage" v-if="equipment.system?.powerUsage?.value" :data-tooltip="game.i18n.localize('ARCHMAGE.GROUPS.powerUsage')">
{{ '' }}
</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 v-if="equipment.system.rarity && equipment.system.rarity !== 'common'">
<span class="rarity">{{ CONFIG.DND5E.itemRarity[equipment.system.rarity] }}</span>
</div>
</div>
</div>
@ -125,6 +143,8 @@ export default {
packIndex: [],
// Filters.
name: '',
type: [],
rarity: [],
chakra: [],
recharge: [],
bonuses: [],
@ -161,6 +181,8 @@ export default {
resetFilters() {
this.sortBy = 'name';
this.name = '';
this.type = [];
this.rarity = [];
this.chakra = [];
this.recharge = [];
this.bonuses = [];
@ -182,8 +204,25 @@ export default {
}
return bonuses;
},
getOptions(config) {
const options = {};
for (let [key, value] of Object.entries(config)) {
options[key] = value.label ?? value;
}
return options;
}
},
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() {
return [
{
@ -283,6 +322,13 @@ export default {
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.
if (Array.isArray(this.chakra) && this.chakra.length > 0) {
// @todo chakra is misspelled in our data model. We need to fix that :(
@ -339,12 +385,10 @@ export default {
// Sort.
result = result.sort((a, b) => {
return a.name.localeCompare(b.name);
});
// Sort.
result = result.sort((a, b) => {
// Add sorts here.
switch (this.sortBy) {
case 'type':
return a.type.localeCompare(b.type);
case 'chakra':
return (a.system?.chackra ?? '').localeCompare((b.system?.chackra ?? ''));
case 'usage':
@ -372,12 +416,14 @@ export default {
], [
'system.activation.type',
'system.armor.type',
'system.container',
'system.damage',
'system.properties',
'system.rarity',
'system.source.book',
'system.type'
]).then(packIndex => {
this.packIndex = packIndex;
this.packIndex = packIndex.filter((e) => !e.system.container);
this.loaded = true;
});