0.4.1k 12-Feb-2021
Auto-populates the visible tab (based on what is meant to be displayed) - Needs a Loading... progress indicator in the data area + remember if data has been loaded already compendium-browser.js - afterRender(): Hook called after app is rendered to populate initial data - _onChangeTab(): Override to load data in this tab when you switch tabs - Move observer to be an instance property en.json - Add tab name tags template.html - Use tab name tagsv0.3.1-spetzel2020
parent
558cd6c8d0
commit
86b15332fe
|
@ -25,6 +25,7 @@
|
||||||
12-Feb-2021 0.4.1j: Correct compactItem for feats and items required display items
|
12-Feb-2021 0.4.1j: Correct compactItem for feats and items required display items
|
||||||
Rename itemType -> browserTab to differentiate candidate item's type from the tab it appears on (spell, feat/class, item, NPC)
|
Rename itemType -> browserTab to differentiate candidate item's type from the tab it appears on (spell, feat/class, item, NPC)
|
||||||
Fixed: Was calling the wrong sort for feat and NPC
|
Fixed: Was calling the wrong sort for feat and NPC
|
||||||
|
0.4.1k: Don't call loadItems() during initalize; getData() just displays static elements
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CMPBrowser = {
|
const CMPBrowser = {
|
||||||
|
@ -36,18 +37,35 @@ const CMPBrowser = {
|
||||||
|
|
||||||
class CompendiumBrowser extends Application {
|
class CompendiumBrowser extends Application {
|
||||||
|
|
||||||
|
static get defaultOptions() {
|
||||||
|
const options = super.defaultOptions;
|
||||||
|
mergeObject(options, {
|
||||||
|
title: "CMPBrowser.compendiumBrowser",
|
||||||
|
tabs: [{navSelector: ".tabs", contentSelector: ".content", initial: "spell"}],
|
||||||
|
classes: options.classes.concat('compendium-browser'),
|
||||||
|
template: "modules/compendium-browser/template/template.html",
|
||||||
|
width: 800,
|
||||||
|
height: 700,
|
||||||
|
resizable: true,
|
||||||
|
minimizable: true
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
// load settings
|
// load settings
|
||||||
if (this.settings === undefined) {
|
if (this.settings === undefined) {
|
||||||
this.initSettings();
|
this.initSettings();
|
||||||
}
|
}
|
||||||
const numToPreload = game.settings.get(CMPBrowser.MODULE_NAME, "preload") ?? CMPBrowser.PRELOAD;
|
const numToPreload = game.settings.get(CMPBrowser.MODULE_NAME, "preload") ?? CMPBrowser.PRELOAD;
|
||||||
|
/*
|
||||||
this.loadItems(numToPreload).then(obj => {
|
this.loadItems(numToPreload).then(obj => {
|
||||||
this.items = obj;
|
this.items = obj;
|
||||||
});
|
});
|
||||||
this.loadNpcs(numToPreload).then(obj => {
|
this.loadNpcs(numToPreload).then(obj => {
|
||||||
this.npcs = obj;
|
this.npcs = obj;
|
||||||
}); //Plug
|
}); //Plug
|
||||||
|
*/
|
||||||
await loadTemplates([
|
await loadTemplates([
|
||||||
"modules/compendium-browser/template/spell-browser.html",
|
"modules/compendium-browser/template/spell-browser.html",
|
||||||
"modules/compendium-browser/template/spell-browser-list.html",
|
"modules/compendium-browser/template/spell-browser-list.html",
|
||||||
|
@ -82,6 +100,347 @@ class CompendiumBrowser extends Application {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hook to load the first data */
|
||||||
|
static afterRender(cb, html, data) {
|
||||||
|
//After rendering the first time or re-rendering trigger the load/reload of visible data
|
||||||
|
if (game.user.isGM || this.settings.allowSpellBrowser) {
|
||||||
|
cb.replaceList(html, "spell");
|
||||||
|
} else if (this.settings.allowFeatBrowser) {
|
||||||
|
cb.replaceList(html, "feat");
|
||||||
|
} else if (this.settings.allowItemBrowser) {
|
||||||
|
cb.replaceList(html, "item");
|
||||||
|
} else if (this.settings.allowNPCBrowser) {
|
||||||
|
cb.replaceList(html, "npc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** override */
|
||||||
|
_onChangeTab(event, tabs, active) {
|
||||||
|
super._onChangeTab(event, tabs, active);
|
||||||
|
const html = this.element;
|
||||||
|
this.replaceList(html, active)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** override */
|
||||||
|
async getData() {
|
||||||
|
//Only called on initial display or refresh (including when settings are changed)
|
||||||
|
const numToPreload = game.settings.get(CMPBrowser.MODULE_NAME, "preload") ?? CMPBrowser.PRELOAD;
|
||||||
|
/*
|
||||||
|
if (!this.spellsLoaded) {
|
||||||
|
// spells will be stored locally to not require full loading each time the browser is opened
|
||||||
|
this.items = await this.loadItems(numToPreload); //also sets this.spellsLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
//0.4.1 Filter as we load to support new way of filtering
|
||||||
|
//Previously loaded all data and filtered in place; now loads minimal (preload) amount, filtered as we go
|
||||||
|
//First time (when you press Compendium Browser button) is called with filters unset
|
||||||
|
|
||||||
|
//0.4.1k: Don't do any item/npc loading until tab is visible
|
||||||
|
let data = {
|
||||||
|
spellFilters : this.spellFilters,
|
||||||
|
showSpellBrowser : (game.user.isGM || this.settings.allowSpellBrowser),
|
||||||
|
featFilters : this.featFilters,
|
||||||
|
showFeatBrowser : (game.user.isGM || this.settings.allowFeatBrowser),
|
||||||
|
itemFilters : this.itemFilters,
|
||||||
|
showItemBrowser : (game.user.isGM || this.settings.allowItemBrowser),
|
||||||
|
npcFilters : this.npcFilters,
|
||||||
|
showNpcBrowser : (game.user.isGM || this.settings.allowNpcBrowser),
|
||||||
|
settings : this.settings,
|
||||||
|
isGM : game.user.isGM
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
activateItemListListeners(html) {
|
||||||
|
// show entity sheet
|
||||||
|
html.find('.item-edit').click(ev => {
|
||||||
|
let itemId = $(ev.currentTarget).parents("li").attr("data-entry-id");
|
||||||
|
let compendium = $(ev.currentTarget).parents("li").attr("data-entry-compendium");
|
||||||
|
let pack = game.packs.find(p => p.collection === compendium);
|
||||||
|
pack.getEntity(itemId).then(entity => {
|
||||||
|
entity.sheet.render(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// make draggable
|
||||||
|
//0.4.1: Avoid the game.packs lookup
|
||||||
|
html.find('.draggable').each((i, li) => {
|
||||||
|
li.setAttribute("draggable", true);
|
||||||
|
li.addEventListener('dragstart', event => {
|
||||||
|
let packName = li.getAttribute("data-entry-compendium");
|
||||||
|
let itemType = li.parents('.tab').data('tab');
|
||||||
|
let pack = game.packs.find(p => p.collection === packName);
|
||||||
|
if (!pack) {
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
event.dataTransfer.setData("text/plain", JSON.stringify({
|
||||||
|
type: pack.entity,
|
||||||
|
pack: pack.collection,
|
||||||
|
id: li.getAttribute("data-entry-id")
|
||||||
|
}));
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** override */
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver((entries, observer) => {
|
||||||
|
for (let e of entries) {
|
||||||
|
if (!e.isIntersecting) continue;
|
||||||
|
const img = e.target;
|
||||||
|
// Avatar image
|
||||||
|
//const img = li.querySelector("img");
|
||||||
|
if (img && img.dataset.src) {
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
delete img.dataset.src;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No longer observe the target
|
||||||
|
observer.unobserve(e.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activateItemListListeners(html);
|
||||||
|
|
||||||
|
// toggle visibility of filter containers
|
||||||
|
html.find('.filtercontainer h3, .multiselect label').click(async ev => {
|
||||||
|
await $(ev.target.nextElementSibling).toggle(100);
|
||||||
|
|
||||||
|
});
|
||||||
|
html.find('.multiselect label').trigger('click');
|
||||||
|
|
||||||
|
// sort spell list
|
||||||
|
html.find('.spell-browser select[name=sortorder]').on('change', ev => {
|
||||||
|
let spellList = html.find('.spell-browser li');
|
||||||
|
let byName = (ev.target.value == 'true');
|
||||||
|
let sortedList = this.sortSpells(spellList, byName);
|
||||||
|
let ol = $(html.find('.spell-browser ul'));
|
||||||
|
ol[0].innerHTML = [];
|
||||||
|
for (let element of sortedList) {
|
||||||
|
ol[0].append(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerSort(html, "spell");
|
||||||
|
|
||||||
|
// sort feat list in place
|
||||||
|
html.find('.feat-browser select[name=sortorder]').on('change', ev => {
|
||||||
|
let featList = html.find('.feat-browser li');
|
||||||
|
let byName = (ev.target.value == 'true');
|
||||||
|
let sortedList = this.sortFeats(featList, byName);
|
||||||
|
let ol = $(html.find('.feat-browser ul'));
|
||||||
|
ol[0].innerHTML = [];
|
||||||
|
for (let element of sortedList) {
|
||||||
|
ol[0].append(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerSort(html, "feat");
|
||||||
|
|
||||||
|
// sort item list in place
|
||||||
|
html.find('.item-browser select[name=sortorder]').on('change', ev => {
|
||||||
|
let itemList = html.find('.item-browser li');
|
||||||
|
let byName = (ev.target.value == 'true');
|
||||||
|
let sortedList = this.sortItems(itemList, byName);
|
||||||
|
let ol = $(html.find('.item-browser ul'));
|
||||||
|
ol[0].innerHTML = [];
|
||||||
|
for (let element of sortedList) {
|
||||||
|
ol[0].append(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerSort(html, "item");
|
||||||
|
|
||||||
|
// sort npc list in place
|
||||||
|
html.find('.npc-browser select[name=sortorder]').on('change', ev => {
|
||||||
|
let npcList = html.find('.npc-browser li');
|
||||||
|
let orderBy = ev.target.value;
|
||||||
|
let sortedList = this.sortNpcs(npcList, orderBy);
|
||||||
|
let ol = $(html.find('.npc-browser ul'));
|
||||||
|
ol[0].innerHTML = [];
|
||||||
|
for (let element of sortedList) {
|
||||||
|
ol[0].append(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerSort(html, "npc");
|
||||||
|
|
||||||
|
// reset filters and re-render
|
||||||
|
html.find('#reset-spell-filter').click(ev => {
|
||||||
|
this.spellFilters.activeFilters = {};
|
||||||
|
this.replaceList(html, "spell");
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find('#reset-feat-filter').click(ev => {
|
||||||
|
this.featFilters.activeFilters = {};
|
||||||
|
this.replaceList(html, "feat");
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find('#reset-item-filter').click(ev => {
|
||||||
|
this.itemFilters.activeFilters = {};
|
||||||
|
this.replaceList(html, "item");
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find('#reset-npc-filter').click(ev => {
|
||||||
|
this.npcFilters.activeFilters = {};
|
||||||
|
this.replaceList(html, "npc");
|
||||||
|
});
|
||||||
|
|
||||||
|
// settings
|
||||||
|
html.find('.settings input').on('change', ev => {
|
||||||
|
let setting = ev.target.dataset.setting;
|
||||||
|
let value = ev.target.checked;
|
||||||
|
if (setting === 'spell-compendium-setting') {
|
||||||
|
let key = ev.target.dataset.key;
|
||||||
|
this.settings.loadedSpellCompendium[key].load = value;
|
||||||
|
//FIXME
|
||||||
|
this.loadItems().then(items => {
|
||||||
|
this.items = items;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
ui.notifications.info("Settings Saved. Item Compendiums are being reloaded.");
|
||||||
|
} else if (setting === 'npc-compendium-setting') {
|
||||||
|
let key = ev.target.dataset.key;
|
||||||
|
this.settings.loadedNpcCompendium[key].load = value;
|
||||||
|
this.loadNpcs().then(npcs => {
|
||||||
|
this.npcs = npcs;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
ui.notifications.info("Settings Saved. NPC Compendiums are being reloaded.");
|
||||||
|
}
|
||||||
|
if (setting === 'allow-spell-browser') {
|
||||||
|
this.settings.allowSpellBrowser = value;
|
||||||
|
}
|
||||||
|
if (setting === 'allow-feat-browser') {
|
||||||
|
this.settings.allowFeatBrowser = value;
|
||||||
|
}
|
||||||
|
if (setting === 'allow-item-browser') {
|
||||||
|
this.settings.allowItemBrowser = value;
|
||||||
|
}
|
||||||
|
if (setting === 'allow-npc-browser') {
|
||||||
|
this.settings.allowNpcBrowser = value;
|
||||||
|
}
|
||||||
|
this.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// activating or deactivating filters
|
||||||
|
//0.4.1: Now does a re-load and updates just the data side
|
||||||
|
// text filters
|
||||||
|
html.find('.filter[data-type=text] input, .filter[data-type=text] select').on('keyup change paste', ev => {
|
||||||
|
const path = $(ev.target).parents('.filter').data('path');
|
||||||
|
const key = path.replace(/\./g, '');
|
||||||
|
const value = ev.target.value;
|
||||||
|
const browserTab = $(ev.target).parents('.tab').data('tab');
|
||||||
|
|
||||||
|
const filterTarget = `${browserTab}Filters`;
|
||||||
|
|
||||||
|
if (value === '' || value === undefined) {
|
||||||
|
delete this[filterTarget].activeFilters[key];
|
||||||
|
} else {
|
||||||
|
this[filterTarget].activeFilters[key] = {
|
||||||
|
path: path,
|
||||||
|
type: 'text',
|
||||||
|
valIsArray: false,
|
||||||
|
value: ev.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.replaceList(html, browserTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// select filters
|
||||||
|
html.find('.filter[data-type=select] select, .filter[data-type=bool] select').on('change', ev => {
|
||||||
|
const path = $(ev.target).parents('.filter').data('path');
|
||||||
|
const key = path.replace(/\./g, '');
|
||||||
|
const filterType = $(ev.target).parents('.filter').data('type');
|
||||||
|
const browserTab = $(ev.target).parents('.tab').data('tab');
|
||||||
|
let valIsArray = $(ev.target).parents('.filter').data('valisarray');
|
||||||
|
if (valIsArray === 'true') valIsArray = true;
|
||||||
|
let value = ev.target.value;
|
||||||
|
if (value === 'false') value = false;
|
||||||
|
if (value === 'true') value = true;
|
||||||
|
|
||||||
|
const filterTarget = `${browserTab}Filters`;
|
||||||
|
|
||||||
|
if (value === "null") {
|
||||||
|
delete this[filterTarget].activeFilters[key]
|
||||||
|
} else {
|
||||||
|
this[filterTarget].activeFilters[key] = {
|
||||||
|
path: path,
|
||||||
|
type: filterType,
|
||||||
|
valIsArray: valIsArray,
|
||||||
|
value:value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.replaceList(html, browserTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// multiselect filters
|
||||||
|
html.find('.filter[data-type=multiSelect] input').on('change', ev => {
|
||||||
|
const path = $(ev.target).parents('.filter').data('path');
|
||||||
|
const key = path.replace(/\./g, '');
|
||||||
|
const filterType = 'multiSelect';
|
||||||
|
const browserTab = $(ev.target).parents('.tab').data('tab');
|
||||||
|
let valIsArray = $(ev.target).parents('.filter').data('valisarray');
|
||||||
|
if (valIsArray === 'true') valIsArray = true;
|
||||||
|
let value = $(ev.target).data('value');
|
||||||
|
|
||||||
|
const filterTarget = `${browserTab}Filters`;
|
||||||
|
const filter = this[filterTarget].activeFilters[key];
|
||||||
|
|
||||||
|
if (ev.target.checked === true) {
|
||||||
|
if (filter === undefined) {
|
||||||
|
this[filterTarget].activeFilters[key] = {
|
||||||
|
path: path,
|
||||||
|
type: filterType,
|
||||||
|
valIsArray: valIsArray,
|
||||||
|
values: [value]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this[filterTarget].activeFilters[key].values.push(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete this[filterTarget].activeFilters[key].values.splice(this[filterTarget].activeFilters[key].values.indexOf(value),1);
|
||||||
|
if (this[filterTarget].activeFilters[key].values.length === 0) {
|
||||||
|
delete this[filterTarget].activeFilters[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.replaceList(html, browserTab, observer);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
html.find('.filter[data-type=numberCompare] select, .filter[data-type=numberCompare] input').on('change keyup paste', ev => {
|
||||||
|
const path = $(ev.target).parents('.filter').data('path');
|
||||||
|
const key = path.replace(/\./g, '');
|
||||||
|
const filterType = 'numberCompare';
|
||||||
|
const browserTab = $(ev.target).parents('.tab').data('tab');
|
||||||
|
let valIsArray = false;
|
||||||
|
|
||||||
|
const operator = $(ev.target).parents('.filter').find('select').val();
|
||||||
|
const value = $(ev.target).parents('.filter').find('input').val();
|
||||||
|
|
||||||
|
const filterTarget = `${browserTab}Filters`;
|
||||||
|
|
||||||
|
if (value === '' || operator === 'null') {
|
||||||
|
delete this[filterTarget].activeFilters[key]
|
||||||
|
} else {
|
||||||
|
this[filterTarget].activeFilters[key] = {
|
||||||
|
path: path,
|
||||||
|
type: filterType,
|
||||||
|
valIsArray: valIsArray,
|
||||||
|
operator: operator,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.replaceList(html, browserTab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async checkListsLoaded() {
|
async checkListsLoaded() {
|
||||||
//Provides extra info not in the standard SRD, like which classes can learn a spell
|
//Provides extra info not in the standard SRD, like which classes can learn a spell
|
||||||
|
@ -395,20 +754,6 @@ class CompendiumBrowser extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static get defaultOptions() {
|
|
||||||
const options = super.defaultOptions;
|
|
||||||
mergeObject(options, {
|
|
||||||
tabs: [{navSelector: ".tabs", contentSelector: ".content", initial: "spell"}],
|
|
||||||
classes: options.classes.concat('compendium-browser'),
|
|
||||||
template: "modules/compendium-browser/template/template.html",
|
|
||||||
width: 800,
|
|
||||||
height: 700,
|
|
||||||
resizable: true,
|
|
||||||
minimizable: true,
|
|
||||||
title: "Compendium Browser"
|
|
||||||
});
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
hookCompendiumList() {
|
hookCompendiumList() {
|
||||||
Hooks.on('renderCompendiumDirectory', (app, html, data) => {
|
Hooks.on('renderCompendiumDirectory', (app, html, data) => {
|
||||||
|
@ -443,336 +788,9 @@ class CompendiumBrowser extends Application {
|
||||||
this.npcFilters.activeFilters = {};
|
this.npcFilters.activeFilters = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData() {
|
|
||||||
const numToPreload = game.settings.get(CMPBrowser.MODULE_NAME, "preload") ?? CMPBrowser.PRELOAD;
|
|
||||||
/*
|
|
||||||
if (!this.spellsLoaded) {
|
|
||||||
// spells will be stored locally to not require full loading each time the browser is opened
|
|
||||||
this.items = await this.loadItems(numToPreload); //also sets this.spellsLoaded
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
//0.4.1 Filter as we load to support new way of filtering
|
|
||||||
//Previously loaded all data and filtered in place; now loads minimal (preload) amount, filtered as we go
|
|
||||||
//First time (when you press Compendium Browser button) is called with filters unset
|
|
||||||
this.items = await this.loadAndFilterItems("spell",numToPreload);
|
|
||||||
if (!this.npcsLoaded) {
|
|
||||||
this.npcs = await this.loadNpcs(numToPreload); //also sets this.npcsLoaded
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = {
|
|
||||||
spells : this.items,
|
|
||||||
spellFilters : this.spellFilters,
|
|
||||||
showSpellBrowser : (game.user.isGM || this.settings.allowSpellBrowser),
|
|
||||||
feats : this.items?.feats,
|
|
||||||
featFilters : this.featFilters,
|
|
||||||
showFeatBrowser : (game.user.isGM || this.settings.allowFeatBrowser),
|
|
||||||
items : this.items?.items,
|
|
||||||
itemFilters : this.itemFilters,
|
|
||||||
showItemBrowser : (game.user.isGM || this.settings.allowItemBrowser),
|
|
||||||
npcs : this.npcs,
|
|
||||||
npcFilters : this.npcFilters,
|
|
||||||
showNpcBrowser : (game.user.isGM || this.settings.allowNpcBrowser),
|
|
||||||
settings : this.settings,
|
|
||||||
isGM : game.user.isGM
|
|
||||||
};
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
activateItemListListeners(html) {
|
|
||||||
// show entity sheet
|
|
||||||
html.find('.item-edit').click(ev => {
|
|
||||||
let itemId = $(ev.currentTarget).parents("li").attr("data-entry-id");
|
|
||||||
let compendium = $(ev.currentTarget).parents("li").attr("data-entry-compendium");
|
|
||||||
let pack = game.packs.find(p => p.collection === compendium);
|
|
||||||
pack.getEntity(itemId).then(entity => {
|
|
||||||
entity.sheet.render(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// make draggable
|
|
||||||
//0.4.1: Avoid the game.packs lookup
|
|
||||||
html.find('.draggable').each((i, li) => {
|
|
||||||
li.setAttribute("draggable", true);
|
|
||||||
li.addEventListener('dragstart', event => {
|
|
||||||
let packName = li.getAttribute("data-entry-compendium");
|
|
||||||
let itemType = li.parents('.tab').data('tab');
|
|
||||||
let pack = game.packs.find(p => p.collection === packName);
|
|
||||||
if (!pack) {
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
event.dataTransfer.setData("text/plain", JSON.stringify({
|
|
||||||
type: pack.entity,
|
|
||||||
pack: pack.collection,
|
|
||||||
id: li.getAttribute("data-entry-id")
|
|
||||||
}));
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
// localizing title
|
|
||||||
$(html).parents('.app').find('.window-title')[0].innerText = game.i18n.localize("CMPBrowser.compendiumBrowser");
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries, observer) => {
|
|
||||||
for (let e of entries) {
|
|
||||||
if (!e.isIntersecting) continue;
|
|
||||||
const img = e.target;
|
|
||||||
// Avatar image
|
|
||||||
//const img = li.querySelector("img");
|
|
||||||
if (img && img.dataset.src) {
|
|
||||||
img.src = img.dataset.src;
|
|
||||||
delete img.dataset.src;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No longer observe the target
|
|
||||||
observer.unobserve(e.target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.activateItemListListeners(html);
|
|
||||||
|
|
||||||
// toggle visibility of filter containers
|
|
||||||
html.find('.filtercontainer h3, .multiselect label').click(async ev => {
|
|
||||||
await $(ev.target.nextElementSibling).toggle(100);
|
|
||||||
|
|
||||||
});
|
|
||||||
html.find('.multiselect label').trigger('click');
|
|
||||||
|
|
||||||
// sort spell list
|
|
||||||
html.find('.spell-browser select[name=sortorder]').on('change', ev => {
|
|
||||||
let spellList = html.find('.spell-browser li');
|
|
||||||
let byName = (ev.target.value == 'true');
|
|
||||||
let sortedList = this.sortSpells(spellList, byName);
|
|
||||||
let ol = $(html.find('.spell-browser ul'));
|
|
||||||
ol[0].innerHTML = [];
|
|
||||||
for (let element of sortedList) {
|
|
||||||
ol[0].append(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.triggerSort(html, "spell");
|
|
||||||
|
|
||||||
// sort feat list in place
|
|
||||||
html.find('.feat-browser select[name=sortorder]').on('change', ev => {
|
|
||||||
let featList = html.find('.feat-browser li');
|
|
||||||
let byName = (ev.target.value == 'true');
|
|
||||||
let sortedList = this.sortFeats(featList, byName);
|
|
||||||
let ol = $(html.find('.feat-browser ul'));
|
|
||||||
ol[0].innerHTML = [];
|
|
||||||
for (let element of sortedList) {
|
|
||||||
ol[0].append(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.triggerSort(html, "feat");
|
|
||||||
|
|
||||||
// sort item list in place
|
|
||||||
html.find('.item-browser select[name=sortorder]').on('change', ev => {
|
|
||||||
let itemList = html.find('.item-browser li');
|
|
||||||
let byName = (ev.target.value == 'true');
|
|
||||||
let sortedList = this.sortItems(itemList, byName);
|
|
||||||
let ol = $(html.find('.item-browser ul'));
|
|
||||||
ol[0].innerHTML = [];
|
|
||||||
for (let element of sortedList) {
|
|
||||||
ol[0].append(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.triggerSort(html, "item");
|
|
||||||
|
|
||||||
// sort npc list in place
|
|
||||||
html.find('.npc-browser select[name=sortorder]').on('change', ev => {
|
|
||||||
let npcList = html.find('.npc-browser li');
|
|
||||||
let orderBy = ev.target.value;
|
|
||||||
let sortedList = this.sortNpcs(npcList, orderBy);
|
|
||||||
let ol = $(html.find('.npc-browser ul'));
|
|
||||||
ol[0].innerHTML = [];
|
|
||||||
for (let element of sortedList) {
|
|
||||||
ol[0].append(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.triggerSort(html, "npc");
|
|
||||||
|
|
||||||
// reset filters and re-render
|
|
||||||
html.find('#reset-spell-filter').click(ev => {
|
|
||||||
this.spellFilters.activeFilters = {};
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
html.find('#reset-feat-filter').click(ev => {
|
|
||||||
this.featFilters.activeFilters = {};
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
html.find('#reset-item-filter').click(ev => {
|
|
||||||
this.itemFilters.activeFilters = {};
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
html.find('#reset-npc-filter').click(ev => {
|
|
||||||
this.npcFilters.activeFilters = {};
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
// settings
|
|
||||||
html.find('.settings input').on('change', ev => {
|
|
||||||
let setting = ev.target.dataset.setting;
|
|
||||||
let value = ev.target.checked;
|
|
||||||
if (setting === 'spell-compendium-setting') {
|
|
||||||
let key = ev.target.dataset.key;
|
|
||||||
this.settings.loadedSpellCompendium[key].load = value;
|
|
||||||
//FIXME
|
|
||||||
this.loadItems().then(items => {
|
|
||||||
this.items = items;
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
ui.notifications.info("Settings Saved. Spell Compendiums are being reloaded.");
|
|
||||||
} else if (setting === 'npc-compendium-setting') {
|
|
||||||
let key = ev.target.dataset.key;
|
|
||||||
this.settings.loadedNpcCompendium[key].load = value;
|
|
||||||
this.loadNpcs().then(npcs => {
|
|
||||||
this.npcs = npcs;
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
ui.notifications.info("Settings Saved. NPC Compendiums are being reloaded.");
|
|
||||||
}
|
|
||||||
if (setting === 'allow-spell-browser') {
|
|
||||||
this.settings.allowSpellBrowser = value;
|
|
||||||
}
|
|
||||||
if (setting === 'allow-feat-browser') {
|
|
||||||
this.settings.allowFeatBrowser = value;
|
|
||||||
}
|
|
||||||
if (setting === 'allow-item-browser') {
|
|
||||||
this.settings.allowItemBrowser = value;
|
|
||||||
}
|
|
||||||
if (setting === 'allow-npc-browser') {
|
|
||||||
this.settings.allowNpcBrowser = value;
|
|
||||||
}
|
|
||||||
this.saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// activating or deactivating filters
|
async replaceList(html, browserTab) {
|
||||||
//0.4.1: Now does a re-load and updates just the data side
|
|
||||||
// text filters
|
|
||||||
html.find('.filter[data-type=text] input, .filter[data-type=text] select').on('keyup change paste', ev => {
|
|
||||||
const path = $(ev.target).parents('.filter').data('path');
|
|
||||||
const key = path.replace(/\./g, '');
|
|
||||||
const value = ev.target.value;
|
|
||||||
const browserTab = $(ev.target).parents('.tab').data('tab');
|
|
||||||
|
|
||||||
const filterTarget = `${browserTab}Filters`;
|
|
||||||
|
|
||||||
if (value === '' || value === undefined) {
|
|
||||||
delete this[filterTarget].activeFilters[key];
|
|
||||||
} else {
|
|
||||||
this[filterTarget].activeFilters[key] = {
|
|
||||||
path: path,
|
|
||||||
type: 'text',
|
|
||||||
valIsArray: false,
|
|
||||||
value: ev.target.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replaceList(html, browserTab, observer);
|
|
||||||
});
|
|
||||||
|
|
||||||
// select filters
|
|
||||||
html.find('.filter[data-type=select] select, .filter[data-type=bool] select').on('change', ev => {
|
|
||||||
const path = $(ev.target).parents('.filter').data('path');
|
|
||||||
const key = path.replace(/\./g, '');
|
|
||||||
const filterType = $(ev.target).parents('.filter').data('type');
|
|
||||||
const browserTab = $(ev.target).parents('.tab').data('tab');
|
|
||||||
let valIsArray = $(ev.target).parents('.filter').data('valisarray');
|
|
||||||
if (valIsArray === 'true') valIsArray = true;
|
|
||||||
let value = ev.target.value;
|
|
||||||
if (value === 'false') value = false;
|
|
||||||
if (value === 'true') value = true;
|
|
||||||
|
|
||||||
const filterTarget = `${browserTab}Filters`;
|
|
||||||
|
|
||||||
if (value === "null") {
|
|
||||||
delete this[filterTarget].activeFilters[key]
|
|
||||||
} else {
|
|
||||||
this[filterTarget].activeFilters[key] = {
|
|
||||||
path: path,
|
|
||||||
type: filterType,
|
|
||||||
valIsArray: valIsArray,
|
|
||||||
value:value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.replaceList(html, browserTab, observer);
|
|
||||||
});
|
|
||||||
|
|
||||||
// multiselect filters
|
|
||||||
html.find('.filter[data-type=multiSelect] input').on('change', ev => {
|
|
||||||
const path = $(ev.target).parents('.filter').data('path');
|
|
||||||
const key = path.replace(/\./g, '');
|
|
||||||
const filterType = 'multiSelect';
|
|
||||||
const browserTab = $(ev.target).parents('.tab').data('tab');
|
|
||||||
let valIsArray = $(ev.target).parents('.filter').data('valisarray');
|
|
||||||
if (valIsArray === 'true') valIsArray = true;
|
|
||||||
let value = $(ev.target).data('value');
|
|
||||||
|
|
||||||
const filterTarget = `${browserTab}Filters`;
|
|
||||||
const filter = this[filterTarget].activeFilters[key];
|
|
||||||
|
|
||||||
if (ev.target.checked === true) {
|
|
||||||
if (filter === undefined) {
|
|
||||||
this[filterTarget].activeFilters[key] = {
|
|
||||||
path: path,
|
|
||||||
type: filterType,
|
|
||||||
valIsArray: valIsArray,
|
|
||||||
values: [value]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this[filterTarget].activeFilters[key].values.push(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete this[filterTarget].activeFilters[key].values.splice(this[filterTarget].activeFilters[key].values.indexOf(value),1);
|
|
||||||
if (this[filterTarget].activeFilters[key].values.length === 0) {
|
|
||||||
delete this[filterTarget].activeFilters[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replaceList(html, browserTab, observer);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
html.find('.filter[data-type=numberCompare] select, .filter[data-type=numberCompare] input').on('change keyup paste', ev => {
|
|
||||||
const path = $(ev.target).parents('.filter').data('path');
|
|
||||||
const key = path.replace(/\./g, '');
|
|
||||||
const filterType = 'numberCompare';
|
|
||||||
const browserTab = $(ev.target).parents('.tab').data('tab');
|
|
||||||
let valIsArray = false;
|
|
||||||
|
|
||||||
const operator = $(ev.target).parents('.filter').find('select').val();
|
|
||||||
const value = $(ev.target).parents('.filter').find('input').val();
|
|
||||||
|
|
||||||
const filterTarget = `${browserTab}Filters`;
|
|
||||||
|
|
||||||
if (value === '' || operator === 'null') {
|
|
||||||
delete this[filterTarget].activeFilters[key]
|
|
||||||
} else {
|
|
||||||
this[filterTarget].activeFilters[key] = {
|
|
||||||
path: path,
|
|
||||||
type: filterType,
|
|
||||||
valIsArray: valIsArray,
|
|
||||||
operator: operator,
|
|
||||||
value: value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replaceList(html, browserTab, observer);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// lazy load images
|
|
||||||
html.find("img").each((i, img) => observer.observe(img));
|
|
||||||
}
|
|
||||||
|
|
||||||
async replaceList(html, browserTab, observer) {
|
|
||||||
let items = null;
|
let items = null;
|
||||||
if (browserTab === 'spell') {
|
if (browserTab === 'spell') {
|
||||||
items = html.find("ul#CBSpells");
|
items = html.find("ul#CBSpells");
|
||||||
|
@ -784,13 +802,16 @@ class CompendiumBrowser extends Application {
|
||||||
items = html.find("ul#CBItems");
|
items = html.find("ul#CBItems");
|
||||||
}
|
}
|
||||||
if (items?.length) {
|
if (items?.length) {
|
||||||
|
//Uses loadAndFilterItems to read compendia for items which pass the current filters and render on this tab
|
||||||
const newItemsHTML = await this.renderItemData(browserTab);
|
const newItemsHTML = await this.renderItemData(browserTab);
|
||||||
items[0].innerHTML = newItemsHTML;
|
items[0].innerHTML = newItemsHTML;
|
||||||
//Re-sort before setting up lazy loading
|
//Re-sort before setting up lazy loading
|
||||||
this.triggerSort(html, browserTab);
|
this.triggerSort(html, browserTab);
|
||||||
|
|
||||||
//Lazy load images
|
//Lazy load images
|
||||||
$(items).find("img").each((i,img) => observer.observe(img));
|
if (this.observer) {
|
||||||
|
$(items).find("img").each((i,img) => this.observer.observe(img));
|
||||||
|
}
|
||||||
|
|
||||||
//Reactivate listeners for clicking and dragging
|
//Reactivate listeners for clicking and dragging
|
||||||
this.activateItemListListeners($(items));
|
this.activateItemListListeners($(items));
|
||||||
|
@ -1410,3 +1431,5 @@ Hooks.on('ready', async () => {
|
||||||
game.compendiumBrowser.addNpcFilters();
|
game.compendiumBrowser.addNpcFilters();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.on("renderCompendiumBrowser", CompendiumBrowser.afterRender);
|
|
@ -55,9 +55,11 @@
|
||||||
"CMPBrowser.dmgInteraction": "Damage Interaction",
|
"CMPBrowser.dmgInteraction": "Damage Interaction",
|
||||||
"CMPBrowser.dmgDealt": "Damage Dealt",
|
"CMPBrowser.dmgDealt": "Damage Dealt",
|
||||||
"CMPBrowser.size": "Size",
|
"CMPBrowser.size": "Size",
|
||||||
"CMPBrowser.spellBrowser":"Spell Browser",
|
"CMPBrowser.Tab.SpellBrowser":"Spell Browser",
|
||||||
"CMPBrowser.npcBrowser":"NPC Browser",
|
"CMPBrowser.Tab.FeatBrowser": "Feat Browser",
|
||||||
"CMPBrowser.settings":"Settings",
|
"CMPBrowser.Tab.ItemBrowser": "Item Browser",
|
||||||
|
"CMPBrowser.Tab.NPCBrowser":"NPC Browser",
|
||||||
|
"CMPBrowser.Tab.Settings":"Settings",
|
||||||
"CMPBrowser.SETTING.Preload.NAME" : "Number to preload",
|
"CMPBrowser.SETTING.Preload.NAME" : "Number to preload",
|
||||||
"CMPBrowser.SETTING.Preload.HINT" : "How many spells, feats, items, and NPCs to preload to keep memory manageable. "
|
"CMPBrowser.SETTING.Preload.HINT" : "How many spells, feats, items, and NPCs to preload to keep memory manageable. "
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="parent">
|
<div class="parent">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
{{#if showSpellBrowser}}<a class="item" data-tab="spell">{{localize "CMPBrowser.spellBrowser"}}</a>{{/if}}
|
{{#if showSpellBrowser}}<a class="item" data-tab="spell">{{localize "CMPBrowser.Tab.SpellBrowser"}}</a>{{/if}}
|
||||||
{{#if showFeatBrowser}}<a class="item" data-tab="feat">Feat Browser</a>{{/if}}
|
{{#if showFeatBrowser}}<a class="item" data-tab="feat">{{localize "CMPBrowser.Tab.FeatBrowser"}}</a>{{/if}}
|
||||||
{{#if showItemBrowser}}<a class="item" data-tab="item">Item Browser</a>{{/if}}
|
{{#if showItemBrowser}}<a class="item" data-tab="item">{{localize "CMPBrowser.Tab.ItemBrowser"}}</a>{{/if}}
|
||||||
{{#if showNpcBrowser}}<a class="item" data-tab="npc">{{localize "CMPBrowser.npcBrowser"}}</a>{{/if}}
|
{{#if showNpcBrowser}}<a class="item" data-tab="npc">{{localize "CMPBrowser.Tab.NPCBrowser"}}</a>{{/if}}
|
||||||
{{#if isGM}}<a class="item" data-tab="setting">{{localize "CMPBrowser.settings"}}</a>{{/if}}
|
{{#if isGM}}<a class="item" data-tab="setting">{{localize "CMPBrowser.Tab.Settings"}}</a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
Loading…
Reference in New Issue