Added SteamAPI and achievement searching

This commit is contained in:
Gnarwhal 2021-02-18 02:15:09 -05:00
parent b229ff9a15
commit 627cc810ed
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
61 changed files with 2781 additions and 903 deletions

View file

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/about.css" />
</head>
<body>
<div id="navbar">
<template data-template="navbar: List<Basic>">
<div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${title}
</div>
</template>
</div>
</template>
</div>
<div id="content-body">
<div id="about-page" class="page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">About</p>
<div class="page-header-separator"></div>
</div>
</div>
<div class="page-subsection">
<div class="page-subsection-wrapper">
<p id="about-text" class="page-subsection-chunk">Collate achievement data from multiple platforms into a single location. Explore achievement data of yourself and others.</p>
</div>
</div>
</div>
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/about.js"></script>
</body>
</html>

View file

@ -28,6 +28,10 @@
<div class="page-header-separator"></div>
</div>
</div>
<div id="importing">
<p id="importing-text">Contemplating...</p>
<img id="importing-loading" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
</div>
<template data-template="profile-page">
<div id="profile-section-1">
<div id="profile-info" class="page-subsection">
@ -49,7 +53,7 @@
</div>
<div id="profile-info-pfp-border" class="page-subsection-chunk">
<div id="profile-info-pfp">
<img id="profile-info-pfp-img" src="/api/user/${id}/image" alt="User's Profile Picture" />
<img id="profile-info-pfp-img" class="lazy-img" data-src="/api/user/${id}/image" alt="User's Profile Picture" />
<div id="profile-info-pfp-vignette"></div>
<img id="profile-info-pfp-upload" src="/static/res/upload.svg" alt="Upload Image" />
<img id="profile-info-pfp-upload-hover" src="/static/res/upload-hover.svg" alt="Upload Image" />

View file

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/search.css" />
<link rel="stylesheet" href="/static/styles/search_achievements.css" />
</head>
<body>
<div id="navbar">
<template data-template="navbar: List<Basic>">
<div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${title}
</div>
</template>
</div>
</template>
</div>
<div id="content-body">
<div id="search-achievements-page" class="search page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">Search Achievements</p>
<div class="page-header-separator"></div>
</div>
</div>
<div class="page-subsection">
<div id="list-page-search-filters">
<div id="list-page-search-dropdown">
<div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label id="achievement-search-button" for="achievement-search">Search</label>
<input id="achievement-search-field" type="text" placeholder="Name" name="achievement-search"/>
</div>
</div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
<div id="filter-dropdown-stack">
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
</div>
</div>
</div>
<div id="list-page-filters-flex">
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Me</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Difficulty</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div class="list-page-filter">
<p class="list-page-filter-label">Min Completion</p>
<input id="min-completion-filter" type="text" class="list-page-filter-param"></input>
</div>
<div class="list-page-filter">
<p class="list-page-filter-label">Max Completion</p>
<input id="max-completion-filter" type="text" class="list-page-filter-param"></input>
</div>
<div class="list-page-filter">
<p class="list-page-filter-label">Min Difficulty</p>
<input id="min-difficulty-filter" type="text" class="list-page-filter-param"></input>
</div>
<div class="list-page-filter">
<p class="list-page-filter-label">Max Difficulty</p>
<input id="max-difficulty-filter" type="text" class="list-page-filter-param"></input>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Quality</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div class="list-page-filter">
<p class="list-page-filter-label">Min Quality</p>
<input id="min-quality-filter" type="text" class="list-page-filter-param"></input>
</div>
<div class="list-page-filter">
<p class="list-page-filter-label">Max Quality</p>
<input id="max-quality-filter" type="text" class="list-page-filter-param"></input>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-partitions">
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-game-name">Game</p>
<p class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-completion">Completion Rate</p>
<p class="list-page-entry-text achievement-difficulty">Difficulty</p>
<p class="list-page-entry-text achievement-quality">Quality</p>
</div>
<template id="achievement-list-template" data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon lazy-img" data-src="/api/achievement/${achievement_id}/image" alt="Achievement Thumbnail"></img>
<p class="list-page-entry-text achievement-game-name">${game_name}</p>
<p class="list-page-entry-text achievement-name">${achievement_name}</p>
<p class="list-page-entry-text achievement-completion">${completion}</p>
<p class="list-page-entry-text achievement-difficulty">${difficulty}</p>
<p class="list-page-entry-text achievement-quality">${quality}</p>
</div>
</template>
</div>
</div>
</div>
</div>
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
</div>
</div>
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/search.js"></script>
<script src="/static/scripts/search_achievements.js"></script>
</body>
</html>

View file

@ -6,7 +6,8 @@
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/index.css" />
<link rel="stylesheet" href="/static/styles/search.css" />
<link rel="stylesheet" href="/static/styles/search_games.css" />
</head>
<body>
<div id="navbar">
@ -21,10 +22,10 @@
</template>
</div>
<div id="content-body">
<div id="index-page" class="page">
<div id="search-games-page" class="search page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">Achievements Project</p>
<p class="page-header-text">Search Games</p>
<div class="page-header-separator"></div>
</div>
</div>
@ -126,6 +127,7 @@
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/index.js"></script>
<script src="/static/scripts/search.js"></script>
<script src="/static/scripts/search_games.js"></script>
</body>
</html>

View file

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/search.css" />
<link rel="stylesheet" href="/static/styles/search_users.css" />
</head>
<body>
<div id="navbar">
<template data-template="navbar: List<Basic>">
<div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${title}
</div>
</template>
</div>
</template>
</div>
<div id="content-body">
<div id="search-users-page" class="search page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">Search Users</p>
<div class="page-header-separator"></div>
</div>
</div>
<div class="page-subsection">
<div id="list-page-search-filters">
<div id="list-page-search-dropdown">
<div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label for="achievement-search">Search</label>
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/>
</div>
</div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
<div id="filter-dropdown-stack">
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
</div>
</div>
</div>
<div id="list-page-filters-flex">
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Games</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">General</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">From My Games</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Platforms</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-partitions">
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-description">Description</p>
<p class="list-page-entry-text achievement-stages">Stages</p>
</div>
<template data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon" src="/static/res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<div class="list-page-entry-text-section">
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/search.js"></script>
<script src="/static/scripts/search_users.js"></script>
</body>
</html>

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M500,904C276.877,904,96,723.12305,96,500S276.877,96,500,96V0C223.8576,0,0,223.8576,0,500c0,276.14233,223.8576,500,500,500A498.43514,498.43514,0,0,0,853.55334,853.55334l-67.8822-67.8822A402.7356,402.7356,0,0,1,500,904Z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View file

@ -25,7 +25,6 @@ const loadSession = async () => {
await fetch(`/api/auth/refresh`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
@ -52,8 +51,10 @@ const commonTemplates = async () => {
{ section: "right" }
]);
template.apply("navbar-section-left").values([
{ item: "project", title: "Project" },
{ item: "about", title: "About" }
{ item: "achievements", title: "Achievements" },
{ item: "users", title: "Users" },
{ item: "games", title: "Games" },
{ item: "import", title: "Import" }
]);
if (session) {
template.apply("navbar-section-right").values([
@ -62,34 +63,40 @@ const commonTemplates = async () => {
]);
} else {
template.apply("navbar-section-right").values([
{ item: "login", title: "Login" }
{ item: "login", title: "Login" }
]);
}
};
const loadLazyImages = () => {
const imgs = document.querySelectorAll(".lazy-img");
for (const img of imgs) {
img.src = img.dataset.src;
}
}
const connectNavbar = () => {
const navItems = document.querySelectorAll(".navbar-item");
if (!session || !session.admin) {
document.querySelector("#navbar-item-import").remove();
}
for (const item of navItems) {
if (item.dataset.pageName === "logout") {
item.addEventListener("click", (clickEvent) => {
fetch(`/api/auth/logout`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: session.key })
})
.then(response => {
session = undefined;
window.location.href = "/login";
});
session = undefined;
window.location.href = "/login";
});
} else if (item.dataset.pageName === "profile") {
item.addEventListener("click", (clickEvent) => window.location.href = `/profile/${session.id}`);
} else if (item.dataset.pageName === "project") {
item.addEventListener("click", (clickEvent) => window.location.href = `/`);
} else {
item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`);
}

View file

@ -1,24 +0,0 @@
const expandTemplates = async () => {
await commonTemplates();
}
const loadFilters = () => {
const filtersButton = document.querySelector("#filter-dropdown-stack");
const filters = document.querySelector("#list-page-filters-flex");
filtersButton.addEventListener("click", (clickEvent) => {
filtersButton.classList.toggle("active");
filters.classList.toggle("active");
});
}
window.addEventListener("load", async (loadEvent) => {
loadRoot();
loadSession();
await expandTemplates();
await template.expand();
connectNavbar();
loadFilters();
});

View file

@ -11,6 +11,7 @@ window.addEventListener("load", async (loadEvent) => {
password: document.querySelector("#password"),
confirm: document.querySelector("#confirm" )
};
fields.email.focus();
const createUser = document.querySelector("#create-user-button");
const login = document.querySelector("#login-button");
@ -80,7 +81,6 @@ window.addEventListener("load", async (loadEvent) => {
freeze();
fetch(`/api/auth/create_user`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
@ -141,7 +141,6 @@ window.addEventListener("load", async (loadEvent) => {
freeze();
fetch(`/api/auth/login`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},

View file

@ -2,14 +2,25 @@ let profileId = window.location.pathname.split('/').pop();
let isReturn = false;
let profileData = null;
const loadProfile = () => {
{
const lists = document.querySelectorAll(".profile-list");
const lists = document.querySelectorAll(".profile-list");
const checkLists = () => {
for (const list of lists) {
if (list.querySelectorAll(".profile-entry").length === 0) {
list.parentElement.removeChild(list);
let found = false;
const entries = list.querySelectorAll(".profile-entry");
for (const entry of entries) {
if (window.getComputedStyle(entry).getPropertyValue('display') !== 'none') {
found = true;
break;
}
}
if (!found) {
list.style.display = 'none';
} else {
list.style.display = 'block';
}
}
}
checkLists();
{
const validImageFile = (type) => {
@ -32,7 +43,6 @@ const loadProfile = () => {
if (usernameField.value !== '') {
fetch(`/api/user/${profileId}/username`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
@ -89,7 +99,6 @@ const loadProfile = () => {
fetch(`/api/user/${profileId}/image`, {
method: 'POST',
mode: 'cors',
body: data
}).then(response => {
if (upload.classList.contains("active")) {
@ -141,6 +150,7 @@ const loadProfile = () => {
for (const platform of platforms) {
platform.classList.toggle("editing");
}
checkLists();
};
editPlatformsButton.addEventListener("click", togglePlatformEdit);
savePlatformsButton.addEventListener("click", togglePlatformEdit);
@ -156,7 +166,6 @@ const loadProfile = () => {
steamButtons[1].addEventListener("click", (clickEvent) => {
fetch(`/api/user/${profileId}/platforms/remove`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
@ -213,11 +222,11 @@ const expandTemplates = async () => {
template.apply("profile-platforms-list").promise(profileData.then(data =>
data.platforms.map(platform => ({
platform_id: platform.id,
img: `<img class="profile-entry-icon" src="/api/platform/image/${platform.id}" alt="Steam Logo" />`,
img: `<img class="profile-entry-icon" src="/api/platform/${platform.id}/image" alt="Steam Logo" />`,
name: platform.name,
connected: platform.connected ? "connected" : "",
add:
(platform.id === 0 ? `<img id="add-steam" class="platform-add" src="https://community.cloudflare.steamstatic.com/public/images/signinthroughsteam/sits_01.png" alt="Add" />` :
(platform.id === 0 ? `<img id="add-steam" class="platform-add" src="https://steamcdn-a.akamaihd.net/steamcommunity/public/images/steamworks_docs/english/sits_small.png" alt="Add" />` :
(platform.id === 1 ? `<p class="platform-unsupported">Coming soon...</p>` :
(platform.id === 2 ? `<p class="platform-unsupported">Coming soon...</p>` :
"")))
@ -228,6 +237,7 @@ const expandTemplates = async () => {
window.addEventListener("load", async (loadEvent) => {
await loadCommon();
var importing = document.querySelector("#importing");
if (!/\d+/.test(profileId)) {
isReturn = true;
const platform = profileId;
@ -238,6 +248,9 @@ window.addEventListener("load", async (loadEvent) => {
delete session.lastProfile;
}
const importingText = importing.querySelector("#importing-text");
importingText.textContent = `Importing from ${platform}...`;
importing.style.display = `flex`;
if (platform === 'steam') {
const query = new URLSearchParams(window.location.search);
@ -246,9 +259,8 @@ window.addEventListener("load", async (loadEvent) => {
} else {
// Regex courtesy of https://github.com/liamcurry/passport-steam/blob/master/lib/passport-steam/strategy.js
var steamId = /^https?:\/\/steamcommunity\.com\/openid\/id\/(\d+)$/.exec(query.get('openid.claimed_id'))[1];
await fetch("/api/user/platforms/add", {
await fetch(`/api/user/${profileId}/platforms/add`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
@ -266,13 +278,15 @@ window.addEventListener("load", async (loadEvent) => {
} else {
// Handle error
}
importing.remove();
profileData = fetch(`/api/user/${profileId}`, { method: 'GET', mode: 'cors' })
profileData = fetch(`/api/user/${profileId}`, { method: 'GET' })
.then(response => response.json());
await expandTemplates();
await template.expand();
loadLazyImages();
connectNavbar();
loadProfile();
});

View file

@ -0,0 +1,26 @@
const expandTemplates = async () => {
await commonTemplates();
}
const loadFilters = () => {
const filtersButton = document.querySelector("#filter-dropdown-stack");
const filtersSection = document.querySelector("#list-page-filters-flex");
filtersButton.addEventListener("click", (clickEvent) => {
filtersButton.classList.toggle("active");
filtersSection.classList.toggle("active");
});
const filterCheckboxes = document.querySelectorAll(".list-page-filter-checkbox");
for (const checkbox of filterCheckboxes) {
checkbox.parentElement.addEventListener("click", (clickEvent) => {
checkbox.parentElement.classList.toggle("selected");
})
}
}
const loadCommonSearch = async () => {
await loadCommon();
await expandTemplates();
};

View file

@ -0,0 +1,108 @@
let templateList = null;
let templateText = null;
const saveTemplate = () => {
const templateElement = document.querySelector("#achievement-list-template");
templateList = templateElement.parentElement;
templateText = templateElement.outerHTML;
templateElement.remove();
};
const loadAchievementSearch = () => {
const loading = document.querySelector("#loading-results");
const searchButton = document.querySelector("#achievement-search-button");
const searchField = document.querySelector("#achievement-search-field" );
const completed = document.querySelector("#completed-filter");
const minCompletion = document.querySelector("#min-completion-filter");
const maxCompletion = document.querySelector("#max-completion-filter");
const minDifficulty = document.querySelector("#min-difficulty-filter");
const maxDifficulty = document.querySelector("#max-difficulty-filter");
const minQuality = document.querySelector("#min-quality-filter" );
const maxQuality = document.querySelector("#max-quality-filter" );
let canSearch = true;
const loadList = async () => {
if (canSearch) {
canSearch = false;
const body = {
searchTerm: searchField.value,
userId: completed.classList.contains('active') ? session.id : null,
completed: completed.classList.contains('active'),
minCompletion: minCompletion.value === '' ? null : Number(minCompletion.value),
maxCompletion: maxCompletion.value === '' ? null : Number(maxCompletion.value),
minDifficulty: minDifficulty.value === '' ? null : Number(minDifficulty.value),
maxDifficulty: maxDifficulty.value === '' ? null : Number(maxDifficulty.value),
minQuality: minQuality.value === '' ? null : Number(minQuality.value ),
maxQuality: maxQuality.value === '' ? null : Number(maxQuality.value ),
};
console.log(body);
let successful = true;
if (Number.isNaN(body.minCompletion)) { successful = false; minCompletion.style.backgroundColor = 'var(--error)'; } else { minCompletion.style.backgroundColor = 'var(--foreground)'; }
if (Number.isNaN(body.maxCompletion)) { successful = false; maxCompletion.style.backgroundColor = 'var(--error)'; } else { maxCompletion.style.backgroundColor = 'var(--foreground)'; }
if (Number.isNaN(body.minDifficulty)) { successful = false; minDifficulty.style.backgroundColor = 'var(--error)'; } else { minDifficulty.style.backgroundColor = 'var(--foreground)'; }
if (Number.isNaN(body.maxDifficulty)) { successful = false; maxDifficulty.style.backgroundColor = 'var(--error)'; } else { maxDifficulty.style.backgroundColor = 'var(--foreground)'; }
if (Number.isNaN(body.minQuality )) { successful = false; minQuality.style.backgroundColor = 'var(--error)'; } else { minQuality.style.backgroundColor = 'var(--foreground)'; }
if (Number.isNaN(body.maxQuality )) { successful = false; maxQuality.style.backgroundColor = 'var(--error)'; } else { maxQuality.style.backgroundColor = 'var(--foreground)'; }
if (!successful) {
canSearch = true;
return;
}
for (const entry of templateList.querySelectorAll(".list-page-entry")) {
entry.remove();
}
templateList.innerHTML += templateText;
loading.style.display = 'block';
const data = fetch("/api/achievements", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(response => response.json())
template.clear();
template.apply('achievements-page-list').promise(data.then(data => data.map(item => ({
achievement_id: item.ID,
achievement_name: item.name,
game_name: item.game,
completion: item.completion == null ? 'N/A' : item.completion + '%',
difficulty: item.difficulty == null ? 'N/A' : item.difficulty + ' / 10',
quality: item.quality == null ? 'N/A' : item.quality + ' / 10'
}))));
await template.expand();
data.then(data => {
loading.style.display = 'none';
canSearch = true;
loadLazyImages();
});
}
};
searchButton.addEventListener("click", loadList);
searchField.addEventListener("keydown", (keyEvent) => {
if (keyEvent.key === 'Enter') {
loadList();
}
});
loadList();
};
window.addEventListener("load", async (loadEvent) => {
await loadCommonSearch();
saveTemplate();
await template.expand();
connectNavbar();
loadFilters();
await loadAchievementSearch();
});

View file

@ -136,6 +136,10 @@ var template = template || {};
}
};
template.clear = () => {
templateEntryMap.clear();
}
const parseType = (type) => {
let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/);
let id = result[1];

View file

@ -70,6 +70,15 @@ html, body {
background-color: var(--accent-value3);
}
@keyframes load {
from { transform: rotateZ(0deg ); }
to { transform: rotateZ(360deg); }
}
.ap-loading {
animation: 1.5s cubic-bezier(0.4, 0.15, 0.6, 0.85) 0s infinite running load;
}
.ap-button {
color: var(--foreground);
background-color: var(--accent-value2);
@ -226,201 +235,3 @@ html, body {
background-color: var(--accent-value3);
}
.list-page-search {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.list-page-search > label,
.list-page-search > input {
box-sizing: border-box;
padding: 12px 20px;
color: var(--foreground);
font-size: 24px;
}
.list-page-search > label {
background-color: var(--accent-value2);
}
.list-page-search > label:hover {
background-color: var(--accent-value3);
}
.list-page-search > label:active {
background-color: var(--accent-value1);
transition-property: background-color;
transition-duration: 0.15s;
}
.list-page-search > input {
background-color: var(--distinction);
border: 0;
flex-grow: 1;
outline: none;
transition-property: background-color, color;
transition-duration: 0.075s;
}
.list-page-search > input:focus {
background-color: var(--foreground);
color: var(--background);
}
.list-page-partitions {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
.list-page-filter-partition {
width: 20%;
max-width: 640px;
}
.list-page-filter-chunk {
background-color: var(--distinction);
width: 100%;
height: 100%;
}
.list-page-filter {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.list-page-filter-checkbox {
width: 28px;
height: 28px;
background-color: var(--foreground);
border: 3px solid var(--foreground);
border-radius: 8px;
transition-property: background-color, border-color;
transition-duration: 0.15s;
}
.list-page-filter:hover > .list-page-filter-checkbox {
background-color: var(--foreground);
border-color: var(--selected-accent1);
}
.list-page-filter.selected > .list-page-filter-checkbox {
background-color: var(--selected-accent1);
border-color: var(--selected-accent1);
}
.list-page-filter.selected:hover > .list-page-filter-checkbox {
background-color: var(--selected-accent0);
border-color: var(--selected-accent1);
}
.list-page-filter-name {
margin: 0;
padding: 16px;
color: var(--foreground);
font-size: 24px;
user-select: none;
}
.list-page-list-partition {
box-sizing: border-box;
flex-grow: 1;
}
.list-page-list {
border-radius: 8px;
overflow: hidden;
}
.list-page-header {
width: 100%;
height: 64px;
background-color: var(--accent-value2);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--foreground);
font-size: 24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-page-entry {
width: 100%;
height: 64px;
background-color: var(--distinction);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--foreground);
font-size: 24px;
}
.list-page-entry-icon {
width: 64px;
height: 64px;
flex-grow: 0;
}
.list-page-entry-text {
box-sizing: border-box;
margin: 0;
padding: 0 12px;
height: 64px;
line-height: 64px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-top: 1px solid var(--background);
}
.list-page-header > .list-page-entry-text {
border: 0;
}

View file

@ -1,111 +0,0 @@
#index-page {
max-width: 1600px;
}
#list-page-search-filters {
width: 100%;
height: max-content;
}
#list-page-search-dropdown {
display: flex;
flex-direction: row;
align-items: center;
}
#search-wrapper {
width: 100%;
}
#list-page-search-pair {
flex-grow: 1;
}
#filter-dropdown-wrapper {
box-sizing: border-box;
height: 84px;
width: 84px;
}
#filter-dropdown-stack {
width: 100%;
height: 100%;
position: relative;
}
#filter-dropdown-stack.active {
transform: rotateZ(-90deg);
}
#filter-dropdown-button {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: block;
}
#filter-dropdown-stack:hover > #filter-dropdown-button {
display: none;
}
#filter-dropdown-button-hover {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: none;
}
#filter-dropdown-stack:hover > #filter-dropdown-button-hover {
display: block;
}
#list-page-filters-flex {
display: none;
width: 100%;
height: max-content;
flex-direction: row;
}
#list-page-filters-flex.active {
display: flex;
}
.list-page-filter-section {
box-sizing: border-box;
flex-basis: 0;
flex-grow: 1;
height: 100%;
display: flex;
flex-direction: column;
}
#list-page-filters-background {
background-color: var(--distinction);
}
.list-page-entry-text.achievement-name {
flex-grow: 3;
flex-basis: 0;
}
.list-page-entry-text.achievement-description {
flex-grow: 6;
flex-basis: 0;
}
.list-page-entry-text.achievement-stages {
flex-grow: 1;
flex-basis: 0;
}

View file

@ -2,8 +2,6 @@
--form-spacing: 48px;
--element-spacing: 12px;
--error: #F95959;
}
#login-page {

View file

@ -2,6 +2,26 @@
max-width: 1600px;
}
#importing {
flex-direction: column;
align-items: center;
display: none;
}
#importing-text {
margin: 0;
height: 96px;
font-size: 64px;
line-height: 96px;
color: var(--foreground);
}
#importing-loading {
height: 64px;
width: 64px;
}
.profile-list {
width: 100%;
height: max-content;
@ -150,7 +170,7 @@
border-radius: 8px;
object-fit: contain;
background-color: var(--background);
background-color: var(--background-dark);
position: absolute;
}
@ -178,7 +198,7 @@
border-radius: 8px;
background-color: var(--background);
background-color: var(--background-dark);
opacity: 0.8;
display: block;

View file

@ -0,0 +1,330 @@
#list-page-search-filters {
width: 100%;
height: max-content;
}
#list-page-search-dropdown {
display: flex;
flex-direction: row;
align-items: center;
}
#search-wrapper {
width: 100%;
}
#list-page-search-pair {
flex-grow: 1;
}
#filter-dropdown-wrapper {
box-sizing: border-box;
height: 84px;
width: 84px;
}
#filter-dropdown-stack {
width: 100%;
height: 100%;
position: relative;
}
#filter-dropdown-stack.active {
transform: rotateZ(-90deg);
}
#filter-dropdown-button {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: block;
}
#filter-dropdown-stack:hover > #filter-dropdown-button {
display: none;
}
#filter-dropdown-button-hover {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: none;
}
#filter-dropdown-stack:hover > #filter-dropdown-button-hover {
display: block;
}
#list-page-filters-flex {
display: none;
width: 100%;
height: max-content;
flex-direction: row;
}
#list-page-filters-flex.active {
display: flex;
}
.list-page-filter-section {
box-sizing: border-box;
flex-basis: max-content;
flex-grow: 1;
height: 100%;
display: flex;
flex-direction: column;
}
.list-page-filter-partition {
width: 20%;
max-width: 640px;
}
.list-page-filter-chunk {
background-color: var(--distinction);
width: 100%;
height: 100%;
}
.list-page-filter {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.list-page-filter-checkbox {
width: 28px;
height: 28px;
background-color: var(--foreground);
border: 3px solid var(--foreground);
border-radius: 8px;
transition-property: background-color, border-color;
transition-duration: 0.15s;
}
.list-page-filter:hover > .list-page-filter-checkbox {
background-color: var(--foreground);
border-color: var(--selected-accent1);
}
.list-page-filter.selected > .list-page-filter-checkbox {
background-color: var(--selected-accent1);
border-color: var(--selected-accent1);
}
.list-page-filter.selected:hover > .list-page-filter-checkbox {
background-color: var(--selected-accent0);
border-color: var(--selected-accent1);
}
.list-page-filter-name,
.list-page-filter-label {
margin: 0;
padding: 16px;
color: var(--foreground);
font-size: 24px;
user-select: none;
}
.list-page-filter-label {
width: 40%;
}
.list-page-filter-param {
padding: 4px;
width: 25%;
font-size: 24px;
color: var(--background);
background-color: var(--foreground);
border-radius: 8px;
border: 0;
outline: none;
}
#list-page-filters-background {
background-color: var(--distinction);
}
.list-page-entry-text {
flex-basis: 0;
}
.page.search {
max-width: 1720px;
}
.list-page-search {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.list-page-search > label,
.list-page-search > input {
box-sizing: border-box;
padding: 12px 20px;
color: var(--foreground);
font-size: 24px;
}
.list-page-search > label {
background-color: var(--accent-value2);
}
.list-page-search > label:hover {
background-color: var(--accent-value3);
}
.list-page-search > label:active {
background-color: var(--accent-value1);
transition-property: background-color;
transition-duration: 0.15s;
}
.list-page-search > input {
background-color: var(--distinction);
border: 0;
flex-grow: 1;
outline: none;
transition-property: background-color, color;
transition-duration: 0.075s;
}
.list-page-search > input:focus {
background-color: var(--foreground);
color: var(--background);
}
.list-page-partitions {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
.list-page-list-partition {
box-sizing: border-box;
flex-grow: 1;
}
.list-page-list {
border-radius: 8px;
overflow: hidden;
}
.list-page-header {
width: 100%;
height: 64px;
background-color: var(--accent-value2);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--foreground);
font-size: 24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-page-entry {
width: 100%;
height: 64px;
background-color: var(--distinction);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--foreground);
font-size: 24px;
}
.list-page-entry-icon {
width: 64px;
height: 64px;
flex-grow: 0;
}
.list-page-entry-text {
box-sizing: border-box;
margin: 0;
padding: 0 12px;
height: 64px;
line-height: 64px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-page-entry > .list-page-entry-text {
border-top: 1px solid var(--background);
border-left: 1px solid var(--background);
}
.list-page-header > .list-page-entry-text {
border-left: 1px solid var(--accent-value0);
}
#loading-results {
margin: 16px 0;
width: 100%;
height: 64px;
object-fit: contain;
display: none;
}

View file

@ -0,0 +1,5 @@
.list-page-entry-text.achievement-game-name { flex-grow: 1.75; }
.list-page-entry-text.achievement-name { flex-grow: 2; }
.list-page-entry-text.achievement-completion { flex-grow: 1; }
.list-page-entry-text.achievement-quality { flex-grow: 1; }
.list-page-entry-text.achievement-difficulty { flex-grow: 1; }

View file

@ -18,4 +18,6 @@
--selected-accent0: #2266CC;
--selected-accent1: #3388FF;
--error: #F95959;
}