Skip to content

Commit

Permalink
Merge pull request #695 from distributive/new-api-illustrators-page
Browse files Browse the repository at this point in the history
New api illustrators page
  • Loading branch information
plural authored Sep 28, 2022
2 parents 121f8bd + 01d8509 commit d524345
Show file tree
Hide file tree
Showing 7 changed files with 398 additions and 93 deletions.
146 changes: 56 additions & 90 deletions app/Resources/views/Banlists/banlists.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

{% block body %}
{% include '/Scripts/api.html.twig' %}
{% include '/Scripts/panels.html.twig' %}
<div class="container" id="banlists">
<h1>{{ block('title') }}</h1>
<div>
Expand Down Expand Up @@ -80,107 +81,72 @@ async function buildBanlistsView() {
$('.temp-loading').remove();
// Add each format to the page
// Start by adding an empty text box for each restriction
formats.forEach(f => {
$(`#tab-pane-${f.id}`).append(`<div class="list"><div class="row"><div id="${f.id}" class="col-sm-12"></div></div></div>`);
const jqCol = $(`#${f.id}`);
const jqPane = $(`#tab-pane-${f.id}`);
const formatRestrictions = restrictions.filter(r => f.attributes.restriction_ids.includes(r.id));
if (formatRestrictions.length == 0) {
jqCol.append(`<p>No cards are currently banned in ${f.attributes.name}. Have a blast!</p>`);
jqPane.append(`<p>No cards are currently banned in ${f.attributes.name}. Have a blast!</p>`);
} else {
jqCol.append('<p><button class="show-all btn btn-secondary">Show all</button><button class="hide-all pull-right btn btn-secondary">Hide all</button></p>');
const panelList = new PanelList(jqPane, null, false, "toggle", "search");
formatRestrictions.forEach(r => {
const active = r.id == f.attributes.active_restriction_id;
const visible = active || r.id == formatRestrictions[0].id;
// Create panel
jqCol.append(`<div id="restriction-${r.id}" class="panel panel-default"></div>`);
const jqPanel = $(`#restriction-${r.id}`);
// Add header
jqPanel.append(`<div class="panel-heading" style="display: flex;"></div>`);
jqPanel.find(`.panel-heading`).append(`<h3 style="margin: 15px 0 10px 0">${r.attributes.name}${active ? " <em>(active)</em>" : ""}</h3>`)
.append(`<button class="list-toggle btn btn-secondary" style="margin-left:auto; margin-top: auto; margin-bottom: auto;">${visible ? 'Hide' : 'Show'}</button>`);
// Add subheader (search link hyphenated for legacy IDs)
jqPanel.append(`<div class="panel-heading" ${visible ? '' : 'style="display: none;"'}><a href=${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':`b!${r.id.replaceAll('_', '-')}`})}>${r.attributes.size} cards</a>. Start Date: ${r.attributes.date_start}.</div>`);
const panel = panelList.createPanel(r.id, visible);
panel.addHeader(r.attributes.name);
panel.addSubheader(`<a href=${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':`b!${r.id.replaceAll('_', '-')}`})}>${r.attributes.size} cards</a>. Start Date: ${r.attributes.date_start}.`);
// Get content data
const v = r.attributes.verdicts;
// Lists (bans, restricted cards, global penalty cards)
const [corpBan, runnerBan] = cards.map(cs => cs.filter(card => v.banned?.includes(card.id)));
const [corpRes, runnerRes] = cards.map(cs => cs.filter(card => v.restricted?.includes(card.id)));
const [corpPen, runnerPen] = cards.map(cs => cs.filter(card => v.global_penalty?.includes(card.id)));
// Mappings (points, universal faction costs)
const [corpUFC, runnerUFC] = cards.map(cs => makeCardMap(cs, v.universal_faction_cost));
const [corpPts, runnerPts] = cards.map(cs => makeCardMap(cs, v.points));
// Add body
panel.addBody();
panel.addBodyContent(`<div class="container-fluid"><div class="row flex-fill"><div class="col-md-6"><h3>Corp Cards</h3><ul class="corp"></ul></div><div class="col-md-6"><h3>Runner Cards</h3><ul class="runner"></ul></div></div></div>`);
// Generate corp restrictions
const jqCorp = panel.body.find('ul.corp');
// Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length)
if (corpBan.length > 0) {
if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents
const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:corp'})}">Current</a></strong> subtype.`;
jqCorp.append(generateList('Banned', removeCurrents(corpBan), pre));
} else {
jqCorp.append(generateList('Banned', corpBan));
}
}
// The others
if (corpRes.length > 0) { jqCorp.append(generateList('Restricted', corpRes)); }
Object.keys(corpUFC).sort().reverse().forEach(p => { jqCorp.append(generateList(`+${p} Universal Influence`, corpUFC[p])); });
if (corpPen.length > 0) { jqCorp.append(generateList('Identity Influence Reduction', corpPen)); }
Object.keys(corpPts).sort().reverse().forEach(p => { jqCorp.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, corpPts[p])); });
// Generate runner restrictions
const jqRunner = panel.body.find('ul.runner');
// Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length)
if (runnerBan.length > 0) {
if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents
const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:runner'})}">Current</a></strong> subtype.`;
jqRunner.append(generateList('Banned', removeCurrents(runnerBan), pre));
} else {
jqRunner.append(generateList('Banned', runnerBan));
}
}
// The others
if (runnerRes.length > 0) { jqRunner.append(generateList('Restricted', runnerRes)); }
Object.keys(runnerUFC).sort().reverse().forEach(p => { jqRunner.append(generateList(`+${p} Universal Influence`, runnerUFC[p])); });
if (runnerPen.length > 0) { jqRunner.append(generateList('Identity Influence Reduction', runnerPen)); }
Object.keys(runnerPts).sort().reverse().forEach(p => { jqRunner.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, runnerPts[p])); });
});
}
});
// Set up event handling
$('.list-toggle').on('click', function (event) {
if ($(this).html() == 'Hide') {
$(this).html('Show');
$(this).closest('.panel').children(':not(:first-child)').hide(250);
} else {
$(this).html('Hide');
$(this).closest('.panel').children().show(250);
}
});
$('.show-all').on('click', function (event) {
const panels = $(this).closest('.list');
panels.find('.panel').children().show();
panels.find('.list-toggle').html('Hide');
});
$('.hide-all').on('click', function (event) {
const panels = $(this).closest('.list');
panels.find('.panel').children(':not(:first-child)').hide();
panels.find('.list-toggle').html('Show');
});
// Add the restriction data to each text box
restrictions.forEach(r => {
const v = r.attributes.verdicts;
// Lists (bans, restricted cards, global penalty cards)
const [corpBan, runnerBan] = cards.map(cs => cs.filter(card => v.banned?.includes(card.id)));
const [corpRes, runnerRes] = cards.map(cs => cs.filter(card => v.restricted?.includes(card.id)));
const [corpPen, runnerPen] = cards.map(cs => cs.filter(card => v.global_penalty?.includes(card.id)));
// Mappings (points, universal faction costs)
const [corpUFC, runnerUFC] = cards.map(cs => makeCardMap(cs, v.universal_faction_cost));
const [corpPts, runnerPts] = cards.map(cs => makeCardMap(cs, v.points));
// Get the panel DOM object
const jqPanel = $(`#restriction-${r.id}`);
// Add body
jqPanel.append(`<div class="panel-body" ${jqPanel.find(`button`).html() == 'Show' ? 'style="display: none;"' : ''}><div class="container-fluid"><div class="row flex-fill">`);
// Generate corp restrictions
jqPanel.find(`.row`).append(`<div class="col-md-6"><h3>Corp Cards</h3><ul id="${r.id}-corp"></ul></div>`);
const jqCorp = $(`#${r.id}-corp`);
// Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length)
if (corpBan.length > 0) {
if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents
const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:corp'})}">Current</a></strong> subtype.`;
jqCorp.append(generateList('Banned', removeCurrents(corpBan), pre));
} else {
jqCorp.append(generateList('Banned', corpBan));
}
}
// The others
if (corpRes.length > 0) { jqCorp.append(generateList('Restricted', corpRes)); }
Object.keys(corpUFC).sort().reverse().forEach(p => { jqCorp.append(generateList(`+${p} Universal Influence`, corpUFC[p])); });
if (corpPen.length > 0) { jqCorp.append(generateList('Identity Influence Reduction', corpPen)); }
Object.keys(corpPts).sort().reverse().forEach(p => { jqCorp.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, corpPts[p])); });
// Generate runner restrictions
jqPanel.find('.row').append(`<div class="col-md-6"><h3>Runner Cards</h3><ul id="${r.id}-runner"></ul></div>`);
const jqRunner = $(`#${r.id}-runner`);
// Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length)
if (runnerBan.length > 0) {
if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents
const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:runner'})}">Current</a></strong> subtype.`;
jqRunner.append(generateList('Banned', removeCurrents(runnerBan), pre));
} else {
jqRunner.append(generateList('Banned', runnerBan));
}
}
// The others
if (runnerRes.length > 0) { jqRunner.append(generateList('Restricted', runnerRes)); }
Object.keys(runnerUFC).sort().reverse().forEach(p => { jqRunner.append(generateList(`+${p} Universal Influence`, runnerUFC[p])); });
if (runnerPen.length > 0) { jqRunner.append(generateList('Identity Influence Reduction', runnerPen)); }
Object.keys(runnerPts).sort().reverse().forEach(p => { jqRunner.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, runnerPts[p])); });
});
}
// Create the banlists view on load
Expand Down
75 changes: 75 additions & 0 deletions app/Resources/views/Illustrators/illustrators.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{% extends '/layout.html.twig' %}

{% block title %}Illustrators{% endblock %}

{% block body %}
{% include '/Scripts/api.html.twig' %}
{% include '/Scripts/panels.html.twig' %}
<div class="container" id="illustrators">
<h1>{{ block('title') }}</h1>
<div>
<p>The world of Netrunner has been brought to life by a wide selection of amazing artists! This page lists all artists whose work has appeared on cards and which cards they illustrated.</p>
</div>
<hr>
<div class="row">
<div id="illustrators" class="col-sm-12">
</div>
</div>
</div>

<script>
async function buildIllustratorsView() {
// Add a temporary loading indicator
$('#illustrators').append(loading_icon);
// Load data from API
const illustrators = await fetchData('{{ v3_api_url }}/api/v3/public/illustrators?include=printings');
// Remove the loading indicator
$('.temp-loading').remove();
// Show the illustrators
const panelList = new PanelList($('#illustrators'), null, true, "search", "sort");
panelList.addSortOption("Name (A-Z)", (panel => panel.title), "ascending");
panelList.addSortOption("Name (Z-A)", (panel => panel.title), "descending");
panelList.addSortOption("Cards (most)", (panel => parseInt(panel.title.match(/\d+(?= card)/)[0])), "descending");
panelList.addSortOption("Cards (least)", (panel => parseInt(panel.title.match(/\d+(?= card)/)[0])), "ascending");
illustrators.forEach(illustrator => {
const panel = panelList.createPanel(illustrator.id, false);
const count = illustrator.relationships.printings.data.length;
const search = Routing.generate('cards_find', {type:'find', 'view':'images', 'q':`i:"${illustrator.attributes.name}"`});
panel.addHeader(`${illustrator.attributes.name} — (<a href="${search}">${count} ${count > 1 ? 'cards' : 'card'}</a>)`);
panel.addSubheader(`<a class="toggle-images" href="#" onClick="return false;">Show images</a>`);
// Format the illustrator's printings into 3 columns
panel.addBody();
panel.onShow = async function() {
const printings = await fetchData(`{{ v3_api_url }}/api/v3/public/illustrators/${illustrator.id}/printings`);
const list = printings.map(p => `<li class="printing-title">${printingToAnchor(p)}</li><a href="${printingToLink(p)}" class="printing-image" style="display: none;"><img alt="Box-E" style="width: 100%; margin-top: 16px;" class="img-responsive lazyloaded" src="${p.attributes.images.nrdb_classic.large}"></a>`);
panel.addBodyContent(`<div class="col-sm-4"><ul>${list.splice(0, Math.ceil(printings.length / 3)).join('')}</ul></div>`);
panel.addBodyContent(`<div class="col-sm-4"><ul>${list.splice(0, Math.ceil(printings.length / 3)).join('')}</ul></div>`);
panel.addBodyContent(`<div class="col-sm-4"><ul>${list.join('')}</ul></div>`);
};
// Enable the image toggle
panel.subheader.find('.toggle-images').on('click', function(event) {
event.stopPropagation();
if ($(this).html() == 'Show images') {
$(this).html('Hide images');
panel.body.find('.printing-title').hide();
panel.body.find('.printing-image').show();
} else {
$(this).html('Show images');
panel.body.find('.printing-title').show();
panel.body.find('.printing-image').hide();
}
});
});
}
// Create the illustrators view on load
buildIllustratorsView();
</script>

{% endblock %}
19 changes: 16 additions & 3 deletions app/Resources/views/Scripts/api.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ async function fetchData(url) {
return await fetchJson(url).then(json => json.data);
}
// Fetches all cards from the v2 API (accounting for pagination)
// Fetches all cards from the API (accounting for pagination)
// Returns a list of cards
async function fetchCards(flags='', pageLimit) {
const data = [];
pageLimit = (pageLimit != undefined) ? `&page[limit]=${pageLimit}` : '';
let json = await fetch(`${v3_api_url}/api/v3/public/cards/${flags}${pageLimit}`).then(data => data.json());
let json = await fetchJson(`${v3_api_url}/api/v3/public/cards/${flags}${pageLimit}`);
data.push(...json.data);
while ('next' in json.links) {
json = await fetch(json.links.next).then(data => data.json());
json = await fetchJson(json.links.next);
data.push(...json.data);
}
return data;
Expand Down Expand Up @@ -69,5 +69,18 @@ function splitBySide(cards) {
function cardToLatestPrintingLink(card) {
return Routing.generate('cards_zoom', {card_code:card.attributes.latest_printing_id});
}
// Creates a simple html link to a given card
function cardToAnchor(card) {
return `<a href="${cardToLink(card)}">${card.attributes.title}</a>`;
}
// Generates a link to a printing
function printingToLink(printing) {
return Routing.generate('cards_zoom', {card_code:printing.id});
}
// Creates a simple html link to a given printing
function printingToAnchor(printing) {
return `<a href="${printingToLink(printing)}">${printing.attributes.title}</a>`;
}
</script>
Loading

0 comments on commit d524345

Please sign in to comment.