/** * Capitalize Helper **/ let capitalize = str => str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; /** * Tooltip Counter */ let tooltipCount = 1; let tooltipCounter = () => tooltipCount++; /** * Discover Classes */ const DISCOVERCLASSES = spells => { let classes = []; spells.forEach(spell => { if (!spell.classes) return; spell.classes.forEach(current => { if (!classes.includes(current)) classes.push(current); }); }); classes = classes.sort((a, b) => a > b); return {spells, classes} }; /** * Dom Helper */ const DOMHELPER = { emphasis (str) { let keywords = ['intelligence', 'int', 'wisdom', 'wis', 'strength', 'str', 'dexterity', 'dex', 'charisma', 'cha', 'comeliness', 'com', 'saving throw', 'ability check', 'skill check']; keywords.forEach(word => { let r = new RegExp(` ${word} `, 'gi'); str = str.replace(r, o => `${o}`); }); str = str.replace(/[\s()<>]+\d+d*\d*(th)*[\s()<>]+/gi, o => `${o}`); return str; }, level (lvl) { let cantrip = lvl.toLowerCase() === 'cantrip'; let invalid = isNaN(lvl) && !cantrip; if (invalid) return '?'; else return cantrip ? 'C' : lvl; }, components (components) { let dom = ''; let types = ['verbal', 'somatic', 'material']; let typesDescriptions = [ 'Spoken word/prayer. Mouth must be free and not mute.', 'Physical gestures, usually hand movements. Must not be bound.', 'Items consumed upon casting spell.' ]; types.forEach((type, i) => { if (!!components[type]) { let currentTooltip = tooltipCounter(); dom += `
  • ${types[i].charAt(0).toUpperCase()}
  • `; } }); return dom; }, classes (classes) { let dom = ''; classes.forEach(x => { dom += `${capitalize(x)} `; }); return dom; } }; /** * List of Selected Spells */ const SELECTEDSPELLS = () => $('#form-spells').serializeArray().map(spell => parseInt(spell.value)); /** * Add Spells to Page */ const RENDERSPELLS = (x) => { let dom = ''; let spells = x ? x.spells.slice(0) : data; spells = CLASSSPELLS(spells); spells = SEARCHSPELLS(spells); spells = spells.sort((a, b) => b.ranking - a.ranking); spells.forEach(spell => { let el = $('#spell-item').html(); let $el = $(el); $('[data-id-spellitem=level]', $el)[0].innerHTML = DOMHELPER.level(spell.level); $('[data-id-spellitem=name]', $el)[0].innerHTML = spell.name; $('[data-id-spellitem=school]', $el)[0].innerHTML = capitalize(spell.school); $('[data-id-spellitem=checkbox]', $el).attr('value', spell.id).attr('id', `sel-${spell.id}`); $('[data-id-spellitem=checklabel]', $el).attr('for', `sel-${spell.id}`); $el.attr('data-action-dialog', spell.id); dom += $el.prop('outerHTML'); }); $('#spell-list').html(dom); componentHandler.upgradeDom(); return data; }; /** * Render Spell Details to Dialog */ const RENDERSPELLDIALOG = spell => { let el = $('#spell-detail').html(); let $el = $(`
    ${el}
    `); $('[data-id-spelldetail=name]', $el)[0].innerHTML = spell.name; $('[data-action-select]', $el).attr('data-action-select', spell.id); $('[data-action-select] input[type="checkbox"]', $el).attr('checked', SELECTEDSPELLS().includes(spell.id)); if (spell.description) { $('[data-id-spelldetail=description]', $el)[0].innerHTML = DOMHELPER.emphasis(spell.description); } if (spell.tags) { $('[data-id-spelldetail=classes]', $el)[0].innerHTML = DOMHELPER.classes(spell.classes); } if (spell.components) { $('[data-id-spelldetail=components]', $el)[0].innerHTML = DOMHELPER.components(spell.components); } if (spell.components.materials_needed) { $('[data-id-spelldetail=materials]', $el)[0].innerHTML = spell.components.materials_needed.join(', '); } if (spell.level) { $('[data-id-spelldetail=level]', $el)[0].innerHTML = 'Level ' + spell.level; } if (spell.duration) { $('[data-id-spelldetail=duration]', $el)[0].innerHTML = spell.duration; } if (spell.casting_time) { $('[data-id-spelldetail=castingtime]', $el)[0].innerHTML = spell.casting_time; } if (spell.range) { $('[data-id-spelldetail=range]', $el)[0].innerHTML = spell.range; } $('#spell-detail-container').html($el.prop('outerHTML')); componentHandler.upgradeDom(); }; /** * Fetch Classes from Spells */ const RENDERCLASSES = data => { let dom = ''; data.classes.forEach(item => { let el = $('#class-toggle').html(); let $el = $(el); $('[data-action-classtoggle]', $el).attr('value', item); $('[data-id-classtoggle=name]', $el)[0].innerHTML = item; dom += $el.prop('outerHTML'); }); $('#class-list').html(dom); return data; }; const SEARCHSPELLS = (spells, search = $('[data-action-search]').val()) => { search = search.toLowerCase(); if (search.length >= 3) { spells = spells.filter(spell => { let regFind = new RegExp(search, 'gi'); let spellName = spell.name.toLowerCase(); let spellDescription = spell.description ? spell.description.toLowerCase() : ''; let spellMaterials = spell.components && spell.components.materials_needed || []; let matches = 0 + spellName.indexOf(search) > -1 ? 20 : 0 + (spellDescription.match(regFind) || []).length + (spellMaterials).filter(com => com.indexOf(search) > -1).length * 10; spell.ranking = matches; return spell.ranking > 0; }); } return spells; }; const CLASSSPELLS = (spells, classes) => { classes = classes || $('#class-list').serializeArray().map(x => x.value); return classes.length ? spells.filter(spell => { for(let i = 0; i < classes.length; i++) { if (spell.classes.indexOf(classes[i]) >= 0) return true; } }) : spells; }; /** * Retrieve Spells */ let data; fetch('./spells.json') .then(response => response.json()) .then(spells => spells.map((spell, i) => { spell.id = i; spell.ranking = 0; // Default to equal ranking return spell; })) .then(spells => data = spells) .then(spells => DISCOVERCLASSES(spells)) .then(data => RENDERCLASSES(data)) .then(data => RENDERSPELLS(data)) .catch(reason => console.error('Unable to retrieve spells list:', reason)); /** * Details Popup */ let $dialog = $('dialog'); if (!$dialog[0].showModal) { dialogPolyfill.registerDialog($dialog); } $('#spell-list').on('click', '[data-action-dialog]', e => { let id = $(e.target).closest('tr').data('action-dialog'); RENDERSPELLDIALOG(data[id]); $dialog[0].showModal(); }).on('click', '.dontprop', e => { e.stopPropagation(); }); $dialog.on('click', '[data-action-close]', () => { $dialog[0].close(); }); /** * Bind Checkbox Selection Action */ $('body').on('change', '[data-action-select] input', e => { let id = $(e.target).closest('label').attr('data-action-select'); if (id) $('#sel-' + id).click(); }); /** * Bind Toggle All Checkbox */ $('label[for=table-header]').on('change', 'input[type="checkbox"]', e => { $(e.target).closest('form').find('[data-id-spellitem=checkbox]').each(function() { this.checked = e.target.checked; if(this.checked) $(this).closest('label').addClass('is-checked'); else $(this).closest('label').removeClass('is-checked'); }) }); /** * Bind Class Filter Switches */ $('body').on('change', '[data-action-classtoggle]', e => { RENDERSPELLS(); }); /** * Search */ $('body').on('change keyup cut paste', '[data-action-search]', e => { setTimeout(function() { RENDERSPELLS(); }, 0); });