Ability Score filters + restyling

pull/12/head
Matheus Clemente 2024-03-31 19:16:45 -03:00
parent 549fa19a4c
commit 855cdcdc1b
3 changed files with 185 additions and 162 deletions

View File

@ -92,7 +92,7 @@ export class CompendiumBrowserVueApplication extends Application {
// Reactivate the listeners if we need to. // Reactivate the listeners if we need to.
if (!this.vueListenersActive) { if (!this.vueListenersActive) {
setTimeout(() => { setTimeout(() => {
this.activateVueListeners($(this.form), true); this.activateVueListeners(true);
}, 150); }, 150);
} }
return; return;
@ -112,7 +112,7 @@ export class CompendiumBrowserVueApplication extends Application {
this.vueRoot = this.vueApp.mount(`[data-appid="${this.appId}"] .compendium-browser-mount`); this.vueRoot = this.vueApp.mount(`[data-appid="${this.appId}"] .compendium-browser-mount`);
// @todo Find a better solution than a timeout. // @todo Find a better solution than a timeout.
setTimeout(() => { setTimeout(() => {
this.activateVueListeners($(this.form), false); this.activateVueListeners();
}, 150); }, 150);
} }
@ -142,37 +142,14 @@ export class CompendiumBrowserVueApplication extends Application {
/** /**
* Activate additional listeners on the rendered Vue app. * Activate additional listeners on the rendered Vue app.
* @param {jQuery} html
*/ */
activateVueListeners(html, repeat = false) { activateVueListeners(repeat = false) {
if (!this.options.editable) { this.vueListenersActive = true;
html.find("input,select,textarea").attr("disabled", true);
return;
}
if (html.find(".archmage-v2-vue").length > 0) {
this.vueListenersActive = true;
}
// Place one-time executions after this line. // Place one-time executions after this line.
if (repeat) return; if (repeat) return;
// Input listeners. // Input listeners.
let inputs = '.section input[type="text"], .section input[type="number"]'; this.element.find(".filtercontainer h3").click(async (ev) => await $(ev.target.nextElementSibling).toggle(100));
html.on("focus", inputs, (event) => this._onFocus(event));
}
/**
* Handle focus events.
*
* @param {*} event
*/
_onFocus(event) {
let target = event.currentTarget;
setTimeout(function () {
if (target == document.activeElement) {
$(target).trigger("select");
}
}, 100);
} }
} }

View File

@ -60,10 +60,69 @@
} }
} }
.control-area { .control-area {
position: sticky;
height: 100%; height: 100%;
overflow: auto;
flex: 2; flex: 2;
.controls {
overflow: hidden auto;
.filtercontainer {
border: 1px solid #bbb;
border-radius: 5px;
margin-top: 5px;
padding: 2px;
h3 {
margin: 0;
cursor: pointer;
}
dl {
margin: 5px 0;
}
div {
margin: 5px 0;
}
dt {
display: inline-block;
width: 40%;
padding-left: 5px;
}
dd {
display: inline-block;
width: 58%;
margin-left: 0;
select {
width: 100%;
}
}
.multiselect {
border: 1px solid #bbb;
border-radius: 3px;
vertical-align: middle;
line-height: 32px;
margin: 2px 0;
label {
padding: 5px;
}
input {
vertical-align: middle;
}
}
.small-input {
width: calc(100% - 44px);
height: 27px;
background: rgba(0, 0, 0, 0.05);
border: 1px solid #444;
border-radius: 3px;
padding: 0 3px;
text-overflow: ellipsis;
}
.small-select {
width: 40px;
}
}
}
footer {
flex: 0;
padding: 1rem 0 3px;
}
button { button {
background: rgba(0, 0, 0, 0.05); background: rgba(0, 0, 0, 0.05);
border: 1px solid #bbb; border: 1px solid #bbb;
@ -71,60 +130,6 @@
margin-top: 5px; margin-top: 5px;
padding: 2px; padding: 2px;
} }
.filtercontainer {
border: 1px solid #bbb;
border-radius: 5px;
margin-top: 5px;
padding: 2px;
h3 {
margin: 0;
cursor: pointer;
}
dl {
margin: 5px 0;
}
div {
margin: 5px 0;
}
dt {
display: inline-block;
width: 40%;
padding-left: 5px;
}
dd {
display: inline-block;
width: 58%;
margin-left: 0;
select {
width: 100%;
}
}
.multiselect {
border: 1px solid #bbb;
border-radius: 3px;
vertical-align: middle;
line-height: 32px;
margin: 2px 0;
label {
padding: 5px;
}
input {
vertical-align: middle;
}
}
.small-input {
width: calc(100% - 44px);
height: 27px;
background: rgba(0, 0, 0, 0.05);
border: 1px solid #444;
border-radius: 3px;
padding: 0 3px;
text-overflow: ellipsis;
}
.small-select {
width: 40px;
}
}
} }
.list-area { .list-area {
position: sticky; position: sticky;
@ -134,17 +139,11 @@
flex: 0; flex: 0;
text-align: center; text-align: center;
} }
}
.browser {
height: 100%;
overflow-y: hidden !important;
.window-content {
overflow-y: hidden !important;
}
ul { ul {
float: right; float: right;
display: block; display: block;
min-width: 335px; // min-width: 335px;
margin: 0; margin: 0;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@ -153,6 +152,10 @@
display: none; display: none;
} }
li { li {
&:hover {
cursor: pointer;
}
span { span {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -178,6 +181,13 @@
} }
} }
} }
}
.browser {
height: 100%;
overflow-y: hidden !important;
.window-content {
overflow-y: hidden !important;
}
.spacer { .spacer {
display: inline-block; display: inline-block;
min-width: 5px; min-width: 5px;
@ -328,6 +338,14 @@
} }
} }
} }
&:hover {
.npc-line {
.npc-name {
text-shadow: 0 0 8px var(--color-shadow-primary);
}
}
}
} }
} }
.settings { .settings {

View File

@ -1,79 +1,98 @@
<template> <template>
<div class="npc-browser browser flexrow"> <div class="npc-browser browser flexrow">
<section class="control-area"> <section class="control-area flexcol">
<div class="filtercontainer"> <div class="controls">
<!-- Name filter. --> <div class="filtercontainer">
<div class="filter"> <!-- Name filter. -->
<input type="text" name="compendiumBrowser.name" v-model="name" :placeholder="game.i18n.localize('Name')" /> <div class="filter">
<input type="text" name="compendiumBrowser.name" v-model="name" :placeholder="game.i18n.localize('Name')" />
</div>
<!-- Sort -->
<dl class="sorter">
<dt>{{ game.i18n.localize('Sort by:') }}</dt>
<dd>
<select class="sort" name="sortorder" v-model="sortBy">
<option v-for="(option, index) in sortOptions" :key="index" :value="option.value">{{ option.label }}</option>
</select>
</dd>
</dl>
</div> </div>
<!-- Sort --> <div class="filtercontainer">
<dl class="sorter"> <h3>{{ game.i18n.localize('General') }}</h3>
<dt>{{ game.i18n.localize('Sort by:') }}</dt> <div class="filters">
<dd> <!-- Level range slider. -->
<select class="sort" name="sortorder" v-model="sortBy"> <div class="filter">
<option v-for="(option, index) in sortOptions" :key="index" :value="option.value">{{ option.label }}</option> <label class="unit-title" for="compendiumBrowser.level">{{ game.i18n.localize('Challenge Rating') }}</label>
</select> <div class="level-range flexrow">
</dd> <div class="level-label"><span>{{ crRange[0] }}</span><span v-if="crRange[0] !== crRange[1]"> - {{ crRange[1] }}</span></div>
</dl> <div class="level-input slider-wrapper flexrow">
<Slider v-model="crRange" :min="0" :max="30" :tooltips="false"/>
<!-- Reset. --> </div>
<button type="reset" @click="resetFilters()">{{ game.i18n.localize('Reset Filters') }}</button> </div>
</div> </div>
<!-- Size filter. -->
<div class="filtercontainer"> <div class="filter">
<h3>{{ game.i18n.localize('General') }}</h3> <label class="unit-title" for="compendiumBrowser.size">{{ game.i18n.localize('Size') }}</label>
<!-- Level range slider. --> <Multiselect
<div class="filter"> v-model="size"
<label class="unit-title" for="compendiumBrowser.level">{{ game.i18n.localize('Challenge Rating') }}</label> mode="tags"
<div class="level-range flexrow"> :searchable="false"
<div class="level-label"><span>{{ crRange[0] }}</span><span v-if="crRange[0] !== crRange[1]"> - {{ crRange[1] }}</span></div> :create-option="false"
<div class="level-input slider-wrapper flexrow"> :options="getOptions(CONFIG.DND5E.actorSizes)"
<Slider v-model="crRange" :min="0" :max="30" :tooltips="false"/> />
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.legact">{{ game.i18n.localize('Legendary Actions') }}</label>
<Multiselect
v-model="legact"
:searchable="false"
:create-option="false"
:options="getOptions(yesNo)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.legres">{{ game.i18n.localize('Legendary Resistances') }}</label>
<Multiselect
v-model="legres"
:searchable="false"
:create-option="false"
:options="getOptions(yesNo)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.creatureType">{{ game.i18n.localize('Creature Type') }}</label>
<Multiselect
v-model="creatureType"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.creatureTypes)"
/>
</div> </div>
</div> </div>
</div> </div>
<!-- Size filter. -->
<div class="filter"> <div class="filtercontainer">
<label class="unit-title" for="compendiumBrowser.size">{{ game.i18n.localize('Size') }}</label> <h3>{{ game.i18n.localize('Ability Scores') }}</h3>
<Multiselect <div class="filters">
v-model="size" <div v-for="(ability, key) in abilities" class="filter">
mode="tags" <label class="unit-title" for="compendiumBrowser.str">{{ ability.label }}</label>
:searchable="false" <div class="level-range flexrow">
:create-option="false" <div class="level-label"><span>{{ ability.range[0] }}</span><span v-if="ability.range[0] !== ability.range[1]"> - {{ ability.range[1] }}</span></div>
:options="getOptions(CONFIG.DND5E.actorSizes)" <div class="level-input slider-wrapper flexrow">
/> <Slider v-model="abilities[key].range" :min="1" :max="30" :tooltips="false"/>
</div> </div>
<div class="filter"> </div>
<label class="unit-title" for="compendiumBrowser.legact">{{ game.i18n.localize('Legendary Actions') }}</label> </div>
<Multiselect </div>
v-model="legact"
:searchable="false"
:create-option="false"
:options="getOptions(yesNo)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.legres">{{ game.i18n.localize('Legendary Resistances') }}</label>
<Multiselect
v-model="legres"
:searchable="false"
:create-option="false"
:options="getOptions(yesNo)"
/>
</div>
<div class="filter">
<label class="unit-title" for="compendiumBrowser.creatureType">{{ game.i18n.localize('Creature Type') }}</label>
<Multiselect
v-model="creatureType"
mode="tags"
:searchable="false"
:create-option="false"
:options="getOptions(CONFIG.DND5E.creatureTypes)"
/>
</div> </div>
</div> </div>
<footer>
<!-- Reset. -->
<button type="reset" @click="resetFilters()">{{ game.i18n.localize('Reset Filters') }}</button>
</footer>
</section> </section>
<div class="list-area flexcol"> <div class="list-area flexcol">
@ -89,9 +108,7 @@
</div> </div>
<div class="npc-line"> <div class="npc-line">
<!-- First row is the title. --> <!-- First row is the title. -->
<div class="npc-name"> <div class="npc-name">[{{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}] {{ entry.name }}</div>
<a>[{{ game.dnd5e.utils.formatCR(entry.system.details.cr) }}] {{ entry.name }}</a>
</div>
<!-- Second row is supplemental info. --> <!-- Second row is supplemental info. -->
<div class="npc-tags flexrow"> <div class="npc-tags flexrow">
<div class="numbers flexrow"> <div class="numbers flexrow">
@ -147,6 +164,8 @@ export default {
} }
}, },
data() { data() {
const abilities = {};
Object.entries(CONFIG.DND5E.abilities).forEach(([k, v]) => abilities[k] = { label: v.label, range: [1, 30] });
return { return {
// Props used for infinite scroll and pagination. // Props used for infinite scroll and pagination.
observer: null, observer: null,
@ -171,6 +190,7 @@ export default {
// Mixed decimals and ints aren't supported by the slider, so // Mixed decimals and ints aren't supported by the slider, so
// just use 0 for all CRs below 1. // just use 0 for all CRs below 1.
crRange: [0, 30], crRange: [0, 30],
abilities,
legact: '', legact: '',
legres: '', legres: '',
size: [], size: [],
@ -267,6 +287,14 @@ export default {
result = result.filter(entry => this.creatureType.includes(entry.system.details.type.value)); result = result.filter(entry => this.creatureType.includes(entry.system.details.type.value));
} }
Object.entries(this.abilities)
.forEach(([k, v]) => {
result = result.filter((entry) =>
Number(entry.system.abilities[k].value) >= v.range[0] &&
Number(entry.system.abilities[k].value) <= v.range[1]
)
});
// Reflow pager. // Reflow pager.
if (result.length > this.pager.perPage) { if (result.length > this.pager.perPage) {
this.pager.totalRows = result.length; this.pager.totalRows = result.length;