feat(character/achievements): add achievements page
This commit is contained in:
parent
ed424d63d0
commit
ab3b84f743
24 changed files with 2419 additions and 33 deletions
243
static/character-achievements.html
Normal file
243
static/character-achievements.html
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
<link rel="stylesheet" type="text/css" href="/css/character-achievements.css">
|
||||
{{> icons }}
|
||||
<script type="application/javascript" src="{{aowow}}/static/widgets/power.js"></script>
|
||||
|
||||
{{> character-header }}
|
||||
|
||||
<div class="columns">
|
||||
<div class="column has-text-centered title is-size-3">
|
||||
<span>Achievement Points:</span>
|
||||
<span id="total-points"></span>
|
||||
<img id="total-points-icon" src="/img/UI-Achievement-TinyShield.png">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-one-fifth-desktop is-full-tablet">
|
||||
<div id="categories"></div>
|
||||
</div>
|
||||
|
||||
<div class="column is-full-tablet-only">
|
||||
<div class="has-text-centered summary title is-size-4">Recent Achievements</div>
|
||||
|
||||
<table id="achievements">
|
||||
<tr id="achievement-template" class="achievement">
|
||||
<td>
|
||||
<div class="iconlarge">
|
||||
<div class="icon"></div>
|
||||
<div class="border"></div>
|
||||
<a target="_blank"></a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a class="name title is-size-5"></a>
|
||||
<div class="description"></div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="points">
|
||||
<span class="value"></span>
|
||||
<span class="earned-date"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="column summary is-two-fifths-desktop is-full-tablet">
|
||||
<div class="has-text-centered title is-size-4">Progress Overview</div>
|
||||
<div id="progress-bars">
|
||||
<div id="progress-template" class="column">
|
||||
<span class="title is-size-5"></span> <span class="value"></span>
|
||||
<br>
|
||||
<progress class="progress is-primary"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
let achievementsData = {};
|
||||
const $achievementTemplate = $("#achievement-template").detach().removeAttr("id");
|
||||
const $progressTemplate = $("#progress-template").detach().removeAttr("id");
|
||||
|
||||
function makeCategoryLink(cat) {
|
||||
const $a = $("<a>")
|
||||
.attr("href", "#")
|
||||
.text(cat.nameLang0)
|
||||
.on("click", () => {
|
||||
const $children = $a.data("children");
|
||||
if ($children !== undefined) {
|
||||
if (!$children.is(":visible")) {
|
||||
// Close the currently selected category
|
||||
$("#categories .open")
|
||||
.slideUp()
|
||||
.removeClass("open");
|
||||
|
||||
// Open the clicked category
|
||||
$children
|
||||
.slideDown()
|
||||
.addClass("open");
|
||||
}
|
||||
} else {
|
||||
openCategory(cat);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return $a;
|
||||
}
|
||||
|
||||
function makeAchievement(achievement) {
|
||||
const isEarned = achievement.id in achievementsData.earned;
|
||||
if (!isEarned && achievement.category === 81) {
|
||||
// Show unlocked feats of strength only
|
||||
return;
|
||||
}
|
||||
|
||||
const $achievement = $achievementTemplate
|
||||
.clone(false)
|
||||
.appendTo($("#achievements"));
|
||||
$achievement.find(".name").text(achievement.title);
|
||||
$achievement.find(".description").text(achievement.description);
|
||||
$achievement.find(".points .value").text(achievement.points);
|
||||
if (achievement.points === 0) {
|
||||
$achievement.find(".points").addClass("no-points");
|
||||
}
|
||||
$achievement.find(".iconlarge .icon").css("background-image", `url("{{aowow}}/static/images/wow/icons/large/${achievement.icon}.jpg")`);
|
||||
$achievement.find("a").attr("href", `{{aowow}}?achievement=${achievement.id}`);
|
||||
|
||||
if (isEarned) {
|
||||
const earned = achievementsData.earned[achievement.id];
|
||||
const date = new Date(earned.date * 1000);
|
||||
$achievement.find(".earned-date").text(date.toLocaleDateString());
|
||||
$achievement.addClass("earned");
|
||||
}
|
||||
|
||||
return $achievement;
|
||||
}
|
||||
|
||||
function openCategory(cat) {
|
||||
const categoryAchievements = achievementsData.achievements.filter(ach => ach.category === cat.id);
|
||||
|
||||
$("#achievements").empty();
|
||||
$(".summary").hide();
|
||||
|
||||
for (const achievement of categoryAchievements) {
|
||||
makeAchievement(achievement);
|
||||
}
|
||||
}
|
||||
|
||||
function openSummary() {
|
||||
$("#achievements").empty();
|
||||
const achievements = Object.keys(achievementsData.earned)
|
||||
.filter(key => achievementsData.earned.hasOwnProperty(key))
|
||||
.map(key => parseInt(key, 10))
|
||||
.map(achievement => {
|
||||
return {
|
||||
achievement,
|
||||
...achievementsData.earned[achievement],
|
||||
};
|
||||
});
|
||||
const mostRecent = achievements.sort((a, b) => b.date - a.date).slice(0, 4);
|
||||
for (const row of mostRecent) {
|
||||
const achievement = achievementsData.achievements.find(ach => ach.id === row.achievement);
|
||||
makeAchievement(achievement);
|
||||
}
|
||||
|
||||
$(".summary").show();
|
||||
}
|
||||
|
||||
function makeProgressBarRow(barsData) {
|
||||
const $row = $("<div>").addClass("columns");
|
||||
$("#progress-bars").append($row);
|
||||
|
||||
for (const bar of barsData) {
|
||||
let $bar = $progressTemplate.clone(false);
|
||||
$bar.find(".title").text(bar.name);
|
||||
$bar.find(".value").text(`${bar.earned} / ${bar.total}`);
|
||||
$bar.find(".progress").attr("value", bar.earned).attr("max", bar.total);
|
||||
$row.append($bar);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
function makeProgressBars() {
|
||||
let totalAchievements = 0;
|
||||
let totalAchievementsEarned = 0;
|
||||
|
||||
const findProgress = (catId) => {
|
||||
const cat = achievementsData.categories.find(cat => cat.id === catId);
|
||||
const subCategories = achievementsData.categories.filter(cat => cat.parent === catId);
|
||||
const categoryAchievements = achievementsData.achievements.filter(ach => ach.category === cat.id || subCategories.some(cat => cat.id === ach.category));
|
||||
let earned = 0;
|
||||
for (const ach of categoryAchievements) {
|
||||
if (ach.id in achievementsData.earned) {
|
||||
++earned;
|
||||
}
|
||||
}
|
||||
totalAchievementsEarned += earned;
|
||||
totalAchievements += categoryAchievements.length;
|
||||
|
||||
return {
|
||||
name: cat.nameLang0,
|
||||
earned,
|
||||
total: categoryAchievements.length,
|
||||
};
|
||||
};
|
||||
makeProgressBarRow([{ ...findProgress(92) }, { ...findProgress(96) }]);
|
||||
makeProgressBarRow([{ ...findProgress(97) }, { ...findProgress(95) }]);
|
||||
makeProgressBarRow([{ ...findProgress(168) }, { ...findProgress(169) }]);
|
||||
makeProgressBarRow([{ ...findProgress(201) }, { ...findProgress(155) }]);
|
||||
const $totalRow = makeProgressBarRow([{ name: "Achievements Earned", earned: totalAchievementsEarned, total: totalAchievements }]);
|
||||
$totalRow.prependTo($("#progress-bars")); // Move the total bar to the top
|
||||
}
|
||||
|
||||
$.getJSON(`/character/{{realm}}/{{guid}}/achievements/data`, (data) => {
|
||||
achievementsData = data;
|
||||
|
||||
const $summaryLink = $("<a>")
|
||||
.attr("href", "#")
|
||||
.text("Summary")
|
||||
.on("click", openSummary)
|
||||
.appendTo($("#categories"));
|
||||
|
||||
for (const cat of achievementsData.categories.filter(c => c.parent < 0)) {
|
||||
if (cat.id === 1) {
|
||||
// Skip Statistics category
|
||||
continue;
|
||||
}
|
||||
const children = achievementsData.categories.filter(c => c.parent === cat.id);
|
||||
|
||||
const $cat = makeCategoryLink(cat);
|
||||
$("#categories").append($cat);
|
||||
|
||||
if (children.length > 0) {
|
||||
const $children = $("<div>")
|
||||
.hide()
|
||||
.addClass("subcategories");
|
||||
$cat.data("children", $children);
|
||||
for (const child of children) {
|
||||
const $child = makeCategoryLink(child);
|
||||
$child.appendTo($children);
|
||||
}
|
||||
|
||||
$("#categories").append($children);
|
||||
}
|
||||
}
|
||||
|
||||
let totalPoints = 0;
|
||||
for (const ach of achievementsData.achievements) {
|
||||
if (ach.id in achievementsData.earned) {
|
||||
totalPoints += ach.points;
|
||||
}
|
||||
}
|
||||
$("#total-points").text(totalPoints);
|
||||
|
||||
makeProgressBars();
|
||||
openSummary();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -20,11 +20,11 @@
|
|||
<span class="name"></span>
|
||||
<span class="points"></span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column"></div>
|
||||
<div class="column"></div>
|
||||
<div class="column"></div>
|
||||
<div class="column"></div>
|
||||
<div class="cols">
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
}
|
||||
|
||||
function createTree(data, learnedTalents, container) {
|
||||
const $columns = $(container).find(".column");
|
||||
const $columns = $(container).find(".col");
|
||||
let points = 0;
|
||||
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@
|
|||
$("#mounts-container").hide();
|
||||
} else {
|
||||
const $dismount = $("#mount-template").clone(false).removeAttr("id");
|
||||
$dismount.find(".icon").css("background-image", `url("/img/UI-GearManager-LeaveItem-small.png")`);
|
||||
$dismount.find(".icon").css("background-image", `url("/img/UI-GearManager-LeaveItem.png")`);
|
||||
$dismount.find("a")
|
||||
.on("click", () => {
|
||||
setMount(0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
html {
|
||||
background-color: #202124;
|
||||
color: white;
|
||||
/* Removes the vertical scrollbar caused by Bulma */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
|
|||
1
static/css/bulma.min.css
vendored
Normal file
1
static/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
101
static/css/character-achievements.css
Normal file
101
static/css/character-achievements.css
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#categories {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
#categories a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.subcategories {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
#achievement-template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#achievements {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 1em 1.5em;
|
||||
}
|
||||
|
||||
.achievement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.achievement td {
|
||||
text-align: center !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.achievement.earned {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.achievement .name {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.achievement .name,
|
||||
.achievement .points,
|
||||
.achievement .description {
|
||||
color: #afafaf;
|
||||
}
|
||||
|
||||
.achievement .icon {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.achievement.earned .icon {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.achievement.earned .name,
|
||||
.achievement.earned .points,
|
||||
.achievement.earned .description {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.achievement .points {
|
||||
background-image: url("/img/UI-Achievement-Shields.png");
|
||||
background-repeat: no-repeat;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: -65px -4px;
|
||||
}
|
||||
|
||||
.achievement.earned .points {
|
||||
background-position: 0px -4px;
|
||||
}
|
||||
|
||||
.achievement .points .value {
|
||||
line-height: 32px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.achievement .earned-date {
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.achievement .points.no-points {
|
||||
background-image: url("/img/UI-Achievement-Shields-NoPoints.png");
|
||||
}
|
||||
|
||||
.achievement .points.no-points .value {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#total-points-icon {
|
||||
position: relative;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
|
@ -79,12 +79,12 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.talent-tree .columns {
|
||||
.talent-tree .cols {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.talent-tree .columns .column {
|
||||
.talent-tree .cols .col {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
top: 6px;
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -2;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wowhead-tooltip .whtt-sellprice {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
top: 4px;
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.iconmedium .border {
|
||||
|
|
@ -50,7 +49,7 @@
|
|||
top: 6px;
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.iconlarge .border {
|
||||
|
|
@ -68,8 +67,14 @@
|
|||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.iconlarge a:hover {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.iconmedium a,
|
||||
.iconlarge a {
|
||||
transition: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,3 +9,7 @@
|
|||
#results td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#results_processing {
|
||||
height: 70px;
|
||||
}
|
||||
|
|
|
|||
BIN
static/img/UI-Achievement-Shields-NoPoints.png
Normal file
BIN
static/img/UI-Achievement-Shields-NoPoints.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
BIN
static/img/UI-Achievement-Shields.png
Normal file
BIN
static/img/UI-Achievement-Shields.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
BIN
static/img/UI-Achievement-TinyShield.png
Normal file
BIN
static/img/UI-Achievement-TinyShield.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 912 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
BIN
static/img/UI-GearManager-LeaveItem.png
Normal file
BIN
static/img/UI-GearManager-LeaveItem.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -6,13 +6,15 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{title}}</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/bulma.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/armory.css">
|
||||
|
||||
<script type="application/javascript" src="/js/sync-url.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="application/javascript" src="/js/sync-url.js"></script>
|
||||
<script type="application/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
{{{ body }}}
|
||||
<section class="section">
|
||||
{{{ body }}}
|
||||
</section>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<a href="/">Back to Armory</a>
|
||||
<br><br>
|
||||
|
||||
<a href="/character/{{realm}}/{{character}}">Character</a> 
|
||||
<a href="/character/{{realm}}/{{character}}/talents">Talents</a> 
|
||||
<a href="/character/{{realm}}/{{character}}/achievements">Achievements</a>
|
||||
<a href="/character/{{realm}}/{{name}}">Character</a> 
|
||||
<a href="/character/{{realm}}/{{name}}/talents">Talents</a> 
|
||||
<a href="/character/{{realm}}/{{name}}/achievements">Achievements</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<div class="char-name">
|
||||
<span class="char-name">{{character}}</span>
|
||||
<span class="char-name">{{name}}</span>
|
||||
</div>
|
||||
<div>
|
||||
Level <span class="char-level">{{level}}</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue