mirror of
https://github.com/artemkorsakov/project-euler-stats.git
synced 2026-07-22 05:42:55 +00:00
Fixed the comments
This commit is contained in:
parent
d8ea37bfb0
commit
f720b25e47
14 changed files with 751 additions and 362 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 89 KiB |
95
helpers/awardsBlock.ts
Normal file
95
helpers/awardsBlock.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { AwardData, AwardBlockData } from './types';
|
||||
import { createProgressBar, createSectionHeader } from './commonBlocks';
|
||||
|
||||
function createAwardTableRow(award: AwardData): HTMLElement {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const awardCell = document.createElement('td');
|
||||
const awardLink = document.createElement('a');
|
||||
awardLink.href = award.link;
|
||||
awardLink.textContent = award.award;
|
||||
awardCell.appendChild(awardLink);
|
||||
row.appendChild(awardCell);
|
||||
|
||||
const descriptionCell = document.createElement('td');
|
||||
descriptionCell.textContent = award.description;
|
||||
row.appendChild(descriptionCell);
|
||||
|
||||
const membersCell = document.createElement('td');
|
||||
membersCell.textContent = award.members;
|
||||
row.appendChild(membersCell);
|
||||
|
||||
const progressCell = document.createElement('td');
|
||||
progressCell.textContent = award.progress;
|
||||
row.appendChild(progressCell);
|
||||
|
||||
const progressBarCell = document.createElement('td');
|
||||
progressBarCell.appendChild(createProgressBar(award.percentage));
|
||||
row.appendChild(progressBarCell);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function createAwardTable(awardData: AwardBlockData): HTMLElement {
|
||||
const table = document.createElement('table');
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
['Award', 'Description', 'Members', 'Progress', 'Progress bar'].forEach(headerText => {
|
||||
const headerCell = document.createElement('th');
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild(headerCell);
|
||||
});
|
||||
tableBody.appendChild(headerRow);
|
||||
|
||||
awardData.awards.forEach(award => {
|
||||
if (!award.isCompleted) {
|
||||
tableBody.appendChild(createAwardTableRow(award));
|
||||
}
|
||||
});
|
||||
|
||||
table.appendChild(tableBody);
|
||||
return table;
|
||||
}
|
||||
|
||||
function createAwardBlock(awardData: AwardBlockData): HTMLElement {
|
||||
const awardBlock = document.createElement('div');
|
||||
|
||||
const awardName = document.createElement('h3');
|
||||
awardName.textContent = awardData.name;
|
||||
awardBlock.appendChild(awardName);
|
||||
|
||||
const completed = awardData.getCompletedCount();
|
||||
const total = awardData.awards.length;
|
||||
|
||||
const completedStatus = document.createElement('h4');
|
||||
completedStatus.textContent = `Status: Won ${completed} out of ${total}`;
|
||||
awardBlock.appendChild(completedStatus);
|
||||
|
||||
const uncompletedStatus = document.createElement('h4');
|
||||
uncompletedStatus.textContent = `Uncompleted awards: ${total - completed}`;
|
||||
awardBlock.appendChild(uncompletedStatus);
|
||||
|
||||
awardBlock.appendChild(createAwardTable(awardData));
|
||||
|
||||
return awardBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the awards table.
|
||||
* @param awardsData - The awards data.
|
||||
* @returns The generated HTMLElement (awards table container).
|
||||
*/
|
||||
export function generateAwardsTableHTML(awardsData: AwardBlockData[]): HTMLElement {
|
||||
const awardsContainer = document.createElement('div');
|
||||
|
||||
const awardsHeader = document.createElement('h2');
|
||||
awardsHeader.textContent = 'Awards';
|
||||
awardsContainer.appendChild(awardsHeader);
|
||||
|
||||
awardsData.forEach(awardData => {
|
||||
awardsContainer.appendChild(createAwardBlock(awardData));
|
||||
});
|
||||
|
||||
return awardsContainer;
|
||||
}
|
||||
30
helpers/commonBlocks.ts
Normal file
30
helpers/commonBlocks.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Creates a progress bar element.
|
||||
* @param percentage - The progress percentage (0 to 100).
|
||||
* @returns The generated HTMLElement (progress bar container).
|
||||
*/
|
||||
export function createProgressBar(percentage: number): HTMLElement {
|
||||
const clampedPercentage = Math.min(100, Math.max(0, percentage));
|
||||
|
||||
const progressContainer = document.createElement('div');
|
||||
progressContainer.className = 'progress-container';
|
||||
|
||||
const progressBar = document.createElement('span');
|
||||
progressBar.className = 'progress-bar';
|
||||
progressBar.style.width = `${clampedPercentage}%`;
|
||||
|
||||
const progressText = document.createElement('span');
|
||||
progressText.className = 'progress-text';
|
||||
progressText.textContent = `${clampedPercentage}%`;
|
||||
|
||||
progressContainer.appendChild(progressBar);
|
||||
progressContainer.appendChild(progressText);
|
||||
|
||||
return progressContainer;
|
||||
}
|
||||
|
||||
export function createSectionHeader(title: string, tagName: 'h2' | 'h4' | 'h5' = 'h2'): HTMLElement {
|
||||
const header = document.createElement(tagName);
|
||||
header.textContent = title;
|
||||
return header;
|
||||
}
|
||||
|
|
@ -8,15 +8,12 @@ import {
|
|||
parseLevelData,
|
||||
parseFriends
|
||||
} from './parsers';
|
||||
import {
|
||||
generateProfileHTML,
|
||||
generateImageHTML,
|
||||
generateProgressTableHTML,
|
||||
generateRatingTableHTML,
|
||||
generateLevelsTableHTML,
|
||||
generateAwardsTableHTML,
|
||||
generateFriendsHTML
|
||||
} from './htmlGenerators';
|
||||
import { generateProfileHTML, generateImageHTML } from './profileBlock';
|
||||
import { generateProgressTableHTML } from './progressBlock';
|
||||
import { generateRatingTableHTML } from './ratingBlock';
|
||||
import { generateLevelsTableHTML } from './levelsBlock';
|
||||
import { generateAwardsTableHTML } from './awardsBlock';
|
||||
import { generateFriendsHTML } from './friendsBlock';
|
||||
import {
|
||||
AccountData,
|
||||
AwardBlockData,
|
||||
|
|
@ -30,20 +27,28 @@ const mainUrl = 'https://projecteuler.net/';
|
|||
const sessionIdName = 'PHPSESSID';
|
||||
const keepAliveName = 'keep_alive';
|
||||
|
||||
export async function fetchProgress(session: string, keep_alive: string, retries: number = 1): Promise<string> {
|
||||
const cookies = `${sessionIdName}=${session}; ${keepAliveName}=${keep_alive}`;
|
||||
console.log(`Fetching progress with cookies: ${cookies}`);
|
||||
/**
|
||||
* Fetches progress data and returns it as an HTMLElement.
|
||||
* @param session - The session cookie value.
|
||||
* @param keep_alive - The keep-alive cookie value.
|
||||
* @param retries - The number of retries in case of failure.
|
||||
* @returns A Promise that resolves to an HTMLElement.
|
||||
*/
|
||||
export async function fetchProgress(session: string, keep_alive: string): Promise<HTMLElement> {
|
||||
const cookies = `${sessionIdName}=${session}; ${keepAliveName}=${keep_alive}`;
|
||||
|
||||
try {
|
||||
return await tryToFetchProgress(cookies);
|
||||
} catch (error) {
|
||||
console.error('Error fetching progress:', error);
|
||||
|
||||
return `<div>Error fetching progress. Please update your cookies in the settings and try again.</div>`;
|
||||
const errorContainer = document.createElement('div');
|
||||
errorContainer.textContent = 'Error fetching progress. Please update your cookies in the settings and try again.';
|
||||
return errorContainer;
|
||||
}
|
||||
}
|
||||
|
||||
async function tryToFetchProgress(cookies: string): Promise<string> {
|
||||
async function tryToFetchProgress(cookies: string): Promise<HTMLElement> {
|
||||
const [
|
||||
accountData,
|
||||
progressData,
|
||||
|
|
@ -104,25 +109,43 @@ async function fetchAwardsData(cookies: string): Promise<AwardBlockData[]> {
|
|||
return parseAwardsData(myAwardsHtml, awardsHtml);
|
||||
}
|
||||
|
||||
function generateHTML(accountData: AccountData, progressData: ProgressData, euleriansPlace: string, locationUrl: string, languageUrl: string, locationRating: RatingData, languageRating: RatingData, levelDataArray: LevelData[], awardsData: AwardBlockData[], friends: FriendData[]): string {
|
||||
const profileHTML = generateProfileHTML(accountData, progressData);
|
||||
console.log("Successfully generated profile HTML.");
|
||||
const imageHTML = generateImageHTML(accountData.account);
|
||||
console.log("Successfully generated image HTML.");
|
||||
const progressHTML = generateProgressTableHTML(accountData, progressData, locationUrl, languageUrl, euleriansPlace, locationRating, languageRating, awardsData);
|
||||
console.log("Successfully generated progress HTML.");
|
||||
const locationTitle = `Progress in <a href="${locationUrl}">the ${accountData.location}'s rating</a>`
|
||||
const locationRatingHTML = generateRatingTableHTML(locationTitle, progressData.solved, locationRating);
|
||||
console.log("Successfully generated location rating HTML.");
|
||||
const languageTitle = `Progress in <a href="${languageUrl}">the ${accountData.language}'s rating</a>`
|
||||
const languageRatingHTML = generateRatingTableHTML(languageTitle, progressData.solved, languageRating);
|
||||
console.log("Successfully generated language rating HTML.");
|
||||
const levelsHTML = generateLevelsTableHTML(progressData, levelDataArray);
|
||||
console.log("Successfully generated levels HTML.");
|
||||
const awardsHTML = generateAwardsTableHTML(awardsData);
|
||||
console.log("Successfully generated awards HTML.");
|
||||
const friendsHTML = generateFriendsHTML(friends, accountData);
|
||||
console.log("Successfully generated friends HTML.");
|
||||
function generateHTML(
|
||||
accountData: AccountData,
|
||||
progressData: ProgressData,
|
||||
euleriansPlace: string,
|
||||
locationUrl: string,
|
||||
languageUrl: string,
|
||||
locationRating: RatingData,
|
||||
languageRating: RatingData,
|
||||
levelDataArray: LevelData[],
|
||||
awardsData: AwardBlockData[],
|
||||
friends: FriendData[]
|
||||
): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
|
||||
return profileHTML + imageHTML + progressHTML + locationRatingHTML + languageRatingHTML + levelsHTML + awardsHTML + friendsHTML;
|
||||
const profileElement = generateProfileHTML(accountData, progressData);
|
||||
container.appendChild(profileElement);
|
||||
|
||||
const imageElement = generateImageHTML(accountData.account);
|
||||
container.appendChild(imageElement);
|
||||
|
||||
const progressElement = generateProgressTableHTML(accountData, progressData, locationUrl, languageUrl, euleriansPlace, locationRating, languageRating, awardsData);
|
||||
container.appendChild(progressElement);
|
||||
|
||||
const locationRatingElement = generateRatingTableHTML(locationUrl, accountData.location, progressData.solved, locationRating);
|
||||
container.appendChild(locationRatingElement);
|
||||
|
||||
const languageRatingElement = generateRatingTableHTML(languageUrl, accountData.language, progressData.solved, languageRating);
|
||||
container.appendChild(languageRatingElement);
|
||||
|
||||
const levelsElement = generateLevelsTableHTML(progressData, levelDataArray);
|
||||
container.appendChild(levelsElement);
|
||||
|
||||
const awardsElement = generateAwardsTableHTML(awardsData);
|
||||
container.appendChild(awardsElement);
|
||||
|
||||
const friendsElement = generateFriendsHTML(friends, accountData);
|
||||
container.appendChild(friendsElement);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
|
|
|||
89
helpers/friendsBlock.ts
Normal file
89
helpers/friendsBlock.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { AccountData, FriendData } from './types';
|
||||
import { createSectionHeader } from './commonBlocks';
|
||||
|
||||
function createFriendTableRow(friend: FriendData, myAccount: FriendData, accountData: AccountData): HTMLElement {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Get the current account
|
||||
if (friend.username === accountData.alias) {
|
||||
row.id = 'your-account';
|
||||
}
|
||||
|
||||
// Rank
|
||||
const rankCell = document.createElement('td');
|
||||
rankCell.textContent = friend.rank;
|
||||
row.appendChild(rankCell);
|
||||
|
||||
// Username
|
||||
const usernameCell = document.createElement('td');
|
||||
const usernameLink = document.createElement('a');
|
||||
usernameLink.href = `https://projecteuler.net/progress=${friend.username}`;
|
||||
usernameLink.textContent = friend.username;
|
||||
usernameCell.appendChild(usernameLink);
|
||||
row.appendChild(usernameCell);
|
||||
|
||||
// Solved tasks
|
||||
const solvedCell = document.createElement('td');
|
||||
solvedCell.textContent = friend.username === accountData.alias
|
||||
? friend.solved.toString()
|
||||
: friend.solved > myAccount.solved
|
||||
? `${friend.solved} (+${friend.solved - myAccount.solved})`
|
||||
: friend.solved.toString();
|
||||
row.appendChild(solvedCell);
|
||||
|
||||
// Level
|
||||
const levelCell = document.createElement('td');
|
||||
levelCell.textContent = friend.username === accountData.alias
|
||||
? friend.level.toString()
|
||||
: friend.level > myAccount.level
|
||||
? `${friend.level} (+${friend.level - myAccount.level})`
|
||||
: friend.level.toString();
|
||||
row.appendChild(levelCell);
|
||||
|
||||
// Awards
|
||||
const awardsCell = document.createElement('td');
|
||||
awardsCell.textContent = friend.username === accountData.alias
|
||||
? friend.awards.toString()
|
||||
: friend.awards > myAccount.awards
|
||||
? `${friend.awards} (+${friend.awards - myAccount.awards})`
|
||||
: friend.awards.toString();
|
||||
row.appendChild(awardsCell);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function createFriendsTable(friends: FriendData[], myAccount: FriendData, accountData: AccountData): HTMLElement {
|
||||
const table = document.createElement('table');
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
['Rank', 'Username', 'Solved', 'Level', 'Awards'].forEach(headerText => {
|
||||
const headerCell = document.createElement('th');
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild(headerCell);
|
||||
});
|
||||
tableBody.appendChild(headerRow);
|
||||
|
||||
friends.forEach(friend => {
|
||||
tableBody.appendChild(createFriendTableRow(friend, myAccount, accountData));
|
||||
});
|
||||
|
||||
table.appendChild(tableBody);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the friends table.
|
||||
* @param friends - The friends data.
|
||||
* @param accountData - The account data.
|
||||
* @returns The generated HTMLElement (friends table container).
|
||||
*/
|
||||
export function generateFriendsHTML(friends: FriendData[], accountData: AccountData): HTMLElement {
|
||||
const friendsContainer = document.createElement('div');
|
||||
friendsContainer.appendChild(createSectionHeader('Friends'));
|
||||
const myAccount = friends.find(friend => friend.username === accountData.alias) ?? new FriendData('-', accountData.alias, 0, 0, 0);
|
||||
friendsContainer.appendChild(createFriendsTable(friends, myAccount, accountData));
|
||||
|
||||
return friendsContainer;
|
||||
}
|
||||
|
||||
|
|
@ -1,316 +0,0 @@
|
|||
import { stringToNumber } from './parsers';
|
||||
import { AccountData, AwardData, AwardBlockData, FriendData, ProgressData, RatingData, LevelData } from './types';
|
||||
|
||||
/**
|
||||
* Generates HTML for the profile section.
|
||||
* @param accountData - The account data.
|
||||
* @param progressData - The progress data.
|
||||
* @returns The generated HTML string.
|
||||
*/
|
||||
export function generateProfileHTML(accountData: AccountData, progressData: ProgressData): string {
|
||||
return `
|
||||
<h2>Profile</h2>
|
||||
<table class="profile-table">
|
||||
<tbody>
|
||||
<tr><td>Account</td><td>${accountData.account}</td></tr>
|
||||
<tr><td>Alias</td><td>${accountData.alias}</td></tr>
|
||||
<tr><td>Location</td><td>${accountData.location}</td></tr>
|
||||
<tr><td>Language</td><td>${accountData.language}</td></tr>
|
||||
<tr><td>Level</td><td>${progressData.level}</td></tr>
|
||||
<tr><td>Solved</td><td>${progressData.solved}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the profile image.
|
||||
* @param account - The account name.
|
||||
* @returns The generated HTML string.
|
||||
*/
|
||||
export function generateImageHTML(account: string): string {
|
||||
const imageSrc = `https://projecteuler.net/profile/${account}.png`;
|
||||
return `<img src="${imageSrc}" alt="Profile ${account}" title="${account}">`;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
message: string;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
export function determineTop(solved: number, rating: RatingData): Result {
|
||||
const thresholds = [
|
||||
{ limit: 100, top: rating.top100, title: 'Top 100' },
|
||||
{ limit: 50, top: rating.top50, title: 'Top 50' },
|
||||
{ limit: 25, top: rating.top25, title: 'Top 25' },
|
||||
{ limit: 10, top: rating.top10, title: 'Top 10' },
|
||||
{ limit: 5, top: rating.top5, title: 'Top 5' },
|
||||
{ limit: 1, top: rating.top1, title: 'Top 1' },
|
||||
];
|
||||
|
||||
for (let i = 0; i < thresholds.length; i++) {
|
||||
const { limit, top, title } = thresholds[i];
|
||||
if (rating.place > limit) {
|
||||
const prevTop = i > 0 ? thresholds[i - 1].top : 0; // Получаем предыдущее значение top
|
||||
const rest = top - solved + 1;
|
||||
const all = top - prevTop + 1;
|
||||
|
||||
const percentage = all > 0 ? Math.round(((all - rest) / all) * 100) : 0;
|
||||
const message = `${rest} problems away from ${title}`;
|
||||
return { message, percentage };
|
||||
}
|
||||
}
|
||||
|
||||
return { message: 'You are in the Top 1', percentage: 100 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция для нахождения AwardData с максимальным значением members.
|
||||
* @param awardBlocks - Массив AwardBlockData.
|
||||
* @returns AwardData с максимальным значением members или null, если не найдено.
|
||||
*/
|
||||
export function findAwardWithMaxMembers(awardBlocks: AwardBlockData[]): AwardData | null {
|
||||
const allAwards = awardBlocks.flatMap(block => block.awards).filter(award => !award.isCompleted);;
|
||||
|
||||
return allAwards.reduce((maxAward, currentAward) => {
|
||||
const currentMembers = parseInt(currentAward.members.replace(/\D/g, '')) || 0;
|
||||
const maxMembers = parseInt(maxAward.members.replace(/\D/g, '')) || 0;
|
||||
|
||||
return currentMembers > maxMembers ? currentAward : maxAward;
|
||||
}, allAwards[0] || null);
|
||||
}
|
||||
|
||||
function createProgressBar(percentage: number): string {
|
||||
const clampedPercentage = Math.min(100, Math.max(0, percentage));
|
||||
|
||||
return `
|
||||
<div class="progress-container">
|
||||
<span class="progress-bar" style="width: ${clampedPercentage}%;"></span>
|
||||
<span class="progress-text">${clampedPercentage}%</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the progress table.
|
||||
* @param accountData - The account data.
|
||||
* @param progressData - The progress data.
|
||||
* @param locationUrl - The URL for the location rating.
|
||||
* @param languageUrl - The URL for the language rating.
|
||||
* @param euleriansPlace - The user's place in Eulerians' rating.
|
||||
* @param locationRating - The location rating data.
|
||||
* @param languageRating - The language rating data.
|
||||
* @returns The generated HTML string.
|
||||
*/
|
||||
export function generateProgressTableHTML(
|
||||
accountData: AccountData,
|
||||
progressData: ProgressData,
|
||||
locationUrl: string,
|
||||
languageUrl: string,
|
||||
euleriansPlace: string,
|
||||
locationRating: RatingData,
|
||||
languageRating: RatingData,
|
||||
awardsData: AwardBlockData[]
|
||||
): string {
|
||||
const locationPlace = locationRating.place > 100 ? 'You are not in the Top 100' : locationRating.place;
|
||||
const languagePlace = languageRating.place > 100 ? 'You are not in the Top 100' : languageRating.place;
|
||||
|
||||
const progressPercentage = createProgressBar(progressData.percentage);
|
||||
|
||||
const locationPlaceToTop = locationRating.place > 100 ? 0 : 100 - locationRating.place;
|
||||
const locationPercentage = createProgressBar(locationPlaceToTop);
|
||||
const languagePlaceToTop = languageRating.place > 100 ? 0 : 100 - languageRating.place;
|
||||
const languagePercentage = createProgressBar(languagePlaceToTop);
|
||||
|
||||
const matchToTheNext = progressData.toTheNext.match(/(\d+)/);
|
||||
const countToTheNext = matchToTheNext ? (25 - parseInt(matchToTheNext[1], 10)) * 4 : 0;
|
||||
const toTheNextPercentage = createProgressBar(countToTheNext);
|
||||
|
||||
const locationResult = determineTop(progressData.solved, locationRating)
|
||||
const locationLiElement = `<li>${accountData.location}: ${locationResult.message}. ${createProgressBar(locationResult.percentage)}</li>`;
|
||||
|
||||
const languageResult = determineTop(progressData.solved, languageRating)
|
||||
const languageLiElement = `<li>${accountData.language}: ${languageResult.message}. ${createProgressBar(languageResult.percentage)}</li>`;
|
||||
|
||||
const award = findAwardWithMaxMembers(awardsData);
|
||||
const awardPlace = award ? `<li>Most unresolved award: <a href="${award.link}">${award.award}</a> (${award.description}) by ${award.members}. ${createProgressBar(award.percentage)}</li>` : '';
|
||||
|
||||
return `
|
||||
<h2>Progress</h2>
|
||||
${progressPercentage}
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Competition</th><th>Status</th><th>Progress bar</th></tr>
|
||||
<tr><td>Progress</td><td>${progressData.progress}</td><td>${progressPercentage}</td></tr>
|
||||
<tr><td>Place in <a href="https://projecteuler.net/eulerians">Eulerians</a></td><td>${euleriansPlace}</td><td></td></tr>
|
||||
<tr><td>Place in <a href="${locationUrl}">${accountData.location}</a></td><td>${locationPlace}</td><td>${locationPercentage}</td></tr>
|
||||
<tr><td>Place in <a href="${languageUrl}">${accountData.language}</a></td><td>${languagePlace}</td><td>${languagePercentage}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Tasks</h3>
|
||||
<ul class="tasks-list">
|
||||
<li>${progressData.level}: ${progressData.toTheNext}. ${toTheNextPercentage}</li>
|
||||
${locationLiElement}
|
||||
${languageLiElement}
|
||||
${awardPlace}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the rating table.
|
||||
* @param title - The title of the rating table.
|
||||
* @param solved - The number of solved problems.
|
||||
* @param rating - The rating data.
|
||||
* @returns The generated HTML string.
|
||||
*/
|
||||
export function generateRatingTableHTML(title: string, solved: number, rating: RatingData): string {
|
||||
const place = rating.place > 100 ? 'You are not in the Top 100' : rating.place;
|
||||
const top1Row = rating.place >= 1 ? `<tr><td>Top 1</td><td>${rating.top1}</td><td>${rating.top1 - solved + 1} problems away from Top 1</td></tr>` : '';
|
||||
const top5Row = rating.place > 5 ? `<tr><td>Top 5</td><td>${rating.top5}</td><td>${rating.top5 - solved + 1} problems away from Top 5</td></tr>` : '';
|
||||
const top10Row = rating.place > 10 ? `<tr><td>Top 10</td><td>${rating.top10}</td><td>${rating.top10 - solved + 1} problems away from Top 10</td></tr>` : '';
|
||||
const top25Row = rating.place > 25 ? `<tr><td>Top 25</td><td>${rating.top25}</td><td>${rating.top25 - solved + 1} problems away from Top 25</td></tr>` : '';
|
||||
const top50Row = rating.place > 50 ? `<tr><td>Top 50</td><td>${rating.top50}</td><td>${rating.top50 - solved + 1} problems away from Top 50</td></tr>` : '';
|
||||
const top100Row = rating.place > 100 ? `<tr><td>Top 100</td><td>${rating.top100}</td><td>${rating.top100 - solved + 1} problems away from Top 100</td></tr>` : '';
|
||||
|
||||
return `
|
||||
<h2>${title}</h2>
|
||||
<h4>Current place: ${place}</h4>
|
||||
<h5>Solved problems: ${solved}</h5>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Competition</th><th>Solved</th><th>Remaining</th></tr>
|
||||
${top100Row}
|
||||
${top50Row}
|
||||
${top25Row}
|
||||
${top10Row}
|
||||
${top5Row}
|
||||
${top1Row}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
export function generateLevelsTableHTML(progressData: ProgressData, levelDataArray: LevelData[]): string {
|
||||
const level = stringToNumber(progressData.level);
|
||||
|
||||
// Проверяем, что levelDataArray не пустой и уровень не выходит за пределы массива
|
||||
if (!levelDataArray.length || level >= levelDataArray.length) {
|
||||
return `<h2>No levels available</h2>`;
|
||||
}
|
||||
|
||||
// Используем slice для получения подмассива, начиная с текущего уровня
|
||||
const slicedArray = levelDataArray.slice(level - 1);
|
||||
|
||||
// Преобразуем каждый элемент в строку <tr>
|
||||
const rows = slicedArray.map(levelData => {
|
||||
const rowLevel = stringToNumber(levelData.level);
|
||||
const needForLevel = rowLevel * 25;
|
||||
const remaining = Math.max(0, needForLevel - progressData.solved);
|
||||
|
||||
return `
|
||||
<tr">
|
||||
<td><a href="https://projecteuler.net/level=${rowLevel}">${levelData.level}</a></td>
|
||||
<td>${needForLevel}</td>
|
||||
<td>${remaining}</td>
|
||||
<td>${levelData.members}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
const allMembers = slicedArray.reduce((total, levelData) => {
|
||||
const membersCount = stringToNumber(levelData.members);
|
||||
return total + (isNaN(membersCount) ? 0 : membersCount);
|
||||
}, 0);
|
||||
|
||||
return `
|
||||
<h2>Level progress</h2>
|
||||
<h4>Current level: ${progressData.level}</h4>
|
||||
<h5>Solved problems: ${progressData.solved}</h5>
|
||||
<h5>Status: ${allMembers} members have made it this far.</h5>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Level</th><th>Solve</th><th>Remaining</th><th>Members</th></tr>
|
||||
${rows}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
export function generateAwardsTableHTML(awardsData: AwardBlockData[]): string {
|
||||
const tables = awardsData.map(awardData => {
|
||||
const awards = awardData.awards;
|
||||
const length = awards.length;
|
||||
|
||||
// Подсчитываем завершенные и создаем строки таблицы за один проход
|
||||
const { completed, rows } = awards.reduce<{ completed: number; rows: string[] }>(
|
||||
(acc, { award, link, description, isCompleted, progress, percentage, members }) => {
|
||||
if (isCompleted) {
|
||||
acc.completed++;
|
||||
} else {
|
||||
const progressBar = createProgressBar(percentage);
|
||||
|
||||
acc.rows.push(`
|
||||
<tr>
|
||||
<td><a href="${link}">${award}</a></td>
|
||||
<td>${description}</td>
|
||||
<td>${members}</td>
|
||||
<td>${progress}</td>
|
||||
<td>${progressBar}</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ completed: 0, rows: [] } // Начальное значение аккумулятора
|
||||
);
|
||||
|
||||
return `
|
||||
<h3>${awardData.name}</h3>
|
||||
<h4>Status: Won ${completed} out of ${length}</h4>
|
||||
<h4>Uncompleted awards: ${length - completed}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Award</th><th>Description</th><th>Members</th><th>Progress</th><th>Progress bar</th></tr>
|
||||
${rows.join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<h2>Awards</h2>
|
||||
${tables}
|
||||
`;
|
||||
}
|
||||
|
||||
export function generateFriendsHTML(friends: FriendData[], accountData: AccountData): string {
|
||||
const myAccount = friends.find(friend => friend.username === accountData.alias) ?? new FriendData( `-`, accountData.alias, 0, 0, 0);
|
||||
|
||||
const rows = friends.map(friend => {
|
||||
const idCss = friend.username === accountData.alias ? 'id="your-account"' : '';
|
||||
const username = `<a href="https://projecteuler.net/progress=${friend.username}">${friend.username}</a>`;
|
||||
const solved = friend.username === accountData.alias ? friend.solved : friend.solved > myAccount.solved ? `${friend.solved} (+${friend.solved - myAccount.solved})` : friend.solved;
|
||||
const level = friend.username === accountData.alias ? friend.level : friend.level > myAccount.level ? `${friend.level} (+${friend.level - myAccount.level})` : friend.level;
|
||||
const awards = friend.username === accountData.alias ? friend.awards : friend.awards > myAccount.awards ? `${friend.awards} (+${friend.awards - myAccount.awards})` : friend.awards;
|
||||
|
||||
return `
|
||||
<tr ${idCss}>
|
||||
<td>${friend.rank}</td>
|
||||
<td>${username}</td>
|
||||
<td>${solved}</td>
|
||||
<td>${level}</td>
|
||||
<td>${awards}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
return `
|
||||
<h2>Friends</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th>Rank</th><th>Username</th><th>Solved</th><th>Level</th><th>Awards</th></tr>
|
||||
${rows.join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
86
helpers/levelsBlock.ts
Normal file
86
helpers/levelsBlock.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { stringToNumber } from './parsers';
|
||||
import { ProgressData, LevelData } from './types';
|
||||
import { createSectionHeader } from './commonBlocks';
|
||||
|
||||
function createLevelTableRow(levelData: LevelData, progressData: ProgressData): HTMLElement {
|
||||
const row = document.createElement('tr');
|
||||
const cell1 = document.createElement('td');
|
||||
const cell2 = document.createElement('td');
|
||||
const cell3 = document.createElement('td');
|
||||
const cell4 = document.createElement('td');
|
||||
|
||||
const rowLevel = stringToNumber(levelData.level);
|
||||
const needForLevel = rowLevel * 25;
|
||||
const remaining = Math.max(0, needForLevel - progressData.solved);
|
||||
|
||||
const levelLink = document.createElement('a');
|
||||
levelLink.href = `https://projecteuler.net/level=${rowLevel}`;
|
||||
levelLink.textContent = levelData.level;
|
||||
|
||||
cell1.appendChild(levelLink);
|
||||
cell2.textContent = needForLevel.toString();
|
||||
cell3.textContent = remaining.toString();
|
||||
cell4.textContent = levelData.members;
|
||||
|
||||
row.appendChild(cell1);
|
||||
row.appendChild(cell2);
|
||||
row.appendChild(cell3);
|
||||
row.appendChild(cell4);
|
||||
return row;
|
||||
}
|
||||
|
||||
function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData[]): HTMLElement {
|
||||
const table = document.createElement('table');
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
['Level', 'Solve', 'Remaining', 'Members'].forEach(headerText => {
|
||||
const headerCell = document.createElement('th');
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild(headerCell);
|
||||
});
|
||||
tableBody.appendChild(headerRow);
|
||||
|
||||
levelDataArray.forEach(levelData => {
|
||||
tableBody.appendChild(createLevelTableRow(levelData, progressData));
|
||||
});
|
||||
|
||||
table.appendChild(tableBody);
|
||||
return table;
|
||||
}
|
||||
|
||||
function calculateTotalMembers(levelDataArray: LevelData[]): number {
|
||||
return levelDataArray.reduce((total, levelData) => {
|
||||
const membersCount = stringToNumber(levelData.members);
|
||||
return total + (isNaN(membersCount) ? 0 : membersCount);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the levels table.
|
||||
* @param progressData - The progress data.
|
||||
* @param levelDataArray - The array of level data.
|
||||
* @returns The generated HTMLElement (levels table container).
|
||||
*/
|
||||
export function generateLevelsTableHTML(progressData: ProgressData, levelDataArray: LevelData[]): HTMLElement {
|
||||
const level = stringToNumber(progressData.level);
|
||||
|
||||
const levelsContainer = document.createElement('div');
|
||||
|
||||
// If there are no levels or the current level is higher than the last available level, display a message
|
||||
if (!levelDataArray.length || level >= levelDataArray.length) {
|
||||
levelsContainer.appendChild(createSectionHeader('No levels available'));
|
||||
return levelsContainer;
|
||||
}
|
||||
|
||||
const slicedArray = levelDataArray.slice(level - 1);
|
||||
levelsContainer.appendChild(createSectionHeader('Level progress'));
|
||||
levelsContainer.appendChild(createSectionHeader(`Current level: ${progressData.level}`, 'h4'));
|
||||
levelsContainer.appendChild(createSectionHeader(`Solved problems: ${progressData.solved}`, 'h5'));
|
||||
const allMembers = calculateTotalMembers(slicedArray);
|
||||
levelsContainer.appendChild(createSectionHeader(`Status: ${allMembers} members have made it this far.`, 'h5'));
|
||||
|
||||
levelsContainer.appendChild(createLevelsTable(progressData, slicedArray));
|
||||
|
||||
return levelsContainer;
|
||||
}
|
||||
|
|
@ -115,10 +115,8 @@ export function parseLevelData(html: string): LevelData[] {
|
|||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
// Находим все элементы, соответствующие селектору
|
||||
const tileBoxes = doc.querySelectorAll('#tile_grid > .tile_box');
|
||||
|
||||
// Используем map для создания массива LevelData
|
||||
return Array.from(tileBoxes).map(tileBox => {
|
||||
const levelElement = tileBox.querySelector('a');
|
||||
const membersElement = tileBox.querySelector('.small_notice');
|
||||
|
|
|
|||
60
helpers/profileBlock.ts
Normal file
60
helpers/profileBlock.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { AccountData, ProgressData } from './types';
|
||||
|
||||
/**
|
||||
* Generates HTML for the profile section.
|
||||
* @param accountData - The account data.
|
||||
* @param progressData - The progress data.
|
||||
* @returns The generated HTMLElement.
|
||||
*/
|
||||
export function generateProfileHTML(accountData: AccountData, progressData: ProgressData): HTMLElement {
|
||||
const profileContainer = document.createElement('div');
|
||||
|
||||
const profileHeader = document.createElement('h2');
|
||||
profileHeader.textContent = 'Profile';
|
||||
profileContainer.appendChild(profileHeader);
|
||||
|
||||
const profileTable = document.createElement('table');
|
||||
profileTable.className = 'profile-table';
|
||||
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const addRow = (label: string, value: string | number) => {
|
||||
const row = document.createElement('tr');
|
||||
const labelCell = document.createElement('td');
|
||||
const valueCell = document.createElement('td');
|
||||
|
||||
labelCell.textContent = label;
|
||||
valueCell.textContent = String(value);
|
||||
|
||||
row.appendChild(labelCell);
|
||||
row.appendChild(valueCell);
|
||||
tableBody.appendChild(row);
|
||||
};
|
||||
|
||||
addRow('Account', accountData.account);
|
||||
addRow('Alias', accountData.alias);
|
||||
addRow('Location', accountData.location);
|
||||
addRow('Language', accountData.language);
|
||||
addRow('Level', progressData.level);
|
||||
addRow('Solved', progressData.solved);
|
||||
|
||||
profileTable.appendChild(tableBody);
|
||||
profileContainer.appendChild(profileTable);
|
||||
|
||||
return profileContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the profile image.
|
||||
* @param account - The account name.
|
||||
* @returns The generated HTMLElement (img element).
|
||||
*/
|
||||
export function generateImageHTML(account: string): HTMLElement {
|
||||
const imageElement = document.createElement('img');
|
||||
|
||||
imageElement.src = `https://projecteuler.net/profile/${account}.png`;
|
||||
imageElement.alt = `Profile ${account}`;
|
||||
imageElement.title = account;
|
||||
|
||||
return imageElement;
|
||||
}
|
||||
241
helpers/progressBlock.ts
Normal file
241
helpers/progressBlock.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { AccountData, AwardData, AwardBlockData, ProgressData, RatingData } from './types';
|
||||
import { createProgressBar, createSectionHeader } from './commonBlocks';
|
||||
|
||||
interface Result {
|
||||
message: string;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
function determineTop(solved: number, rating: RatingData): Result {
|
||||
const thresholds = [
|
||||
{ limit: 100, top: rating.top100, title: 'Top 100' },
|
||||
{ limit: 50, top: rating.top50, title: 'Top 50' },
|
||||
{ limit: 25, top: rating.top25, title: 'Top 25' },
|
||||
{ limit: 10, top: rating.top10, title: 'Top 10' },
|
||||
{ limit: 5, top: rating.top5, title: 'Top 5' },
|
||||
{ limit: 1, top: rating.top1, title: 'Top 1' },
|
||||
];
|
||||
|
||||
for (let i = 0; i < thresholds.length; i++) {
|
||||
const { limit, top, title } = thresholds[i];
|
||||
if (rating.place > limit) {
|
||||
const prevTop = i > 0 ? thresholds[i - 1].top : 0; // Get the previous top
|
||||
const rest = top - solved + 1;
|
||||
const all = top - prevTop + 1;
|
||||
|
||||
const percentage = all > 0 ? Math.round(((all - rest) / all) * 100) : 0;
|
||||
const message = `${rest} problems away from ${title}`;
|
||||
return { message, percentage };
|
||||
}
|
||||
}
|
||||
|
||||
return { message: 'You are in the Top 1', percentage: 100 };
|
||||
}
|
||||
|
||||
/** Function to determine AwardData with the maximum number of members.
|
||||
*
|
||||
* @param awardBlocks - AwardBlockData array.
|
||||
* @returns AwardData with the maximum number of members or null if there are no unfinished awards.
|
||||
*/
|
||||
function findAwardWithMaxMembers(awardBlocks: AwardBlockData[]): AwardData | null {
|
||||
const allAwards = awardBlocks.flatMap(block => block.awards).filter(award => !award.isCompleted);;
|
||||
|
||||
return allAwards.reduce((maxAward, currentAward) => {
|
||||
const currentMembers = parseInt(currentAward.members.replace(/\D/g, '')) || 0;
|
||||
const maxMembers = parseInt(maxAward.members.replace(/\D/g, '')) || 0;
|
||||
|
||||
return currentMembers > maxMembers ? currentAward : maxAward;
|
||||
}, allAwards[0] || null);
|
||||
}
|
||||
|
||||
function createTableRow(label: string | HTMLElement, status: string, progressBar?: HTMLElement): HTMLElement {
|
||||
const row = document.createElement('tr');
|
||||
const labelCell = document.createElement('td');
|
||||
const statusCell = document.createElement('td');
|
||||
const progressCell = document.createElement('td');
|
||||
|
||||
// If label is a string, set the text content directly
|
||||
if (typeof label === 'string') {
|
||||
labelCell.textContent = label;
|
||||
} else { // If label is an HTMLElement, append it to the cell
|
||||
labelCell.appendChild(label);
|
||||
}
|
||||
|
||||
statusCell.textContent = status;
|
||||
|
||||
if (progressBar) {
|
||||
progressCell.appendChild(progressBar);
|
||||
}
|
||||
|
||||
row.appendChild(labelCell);
|
||||
row.appendChild(statusCell);
|
||||
row.appendChild(progressCell);
|
||||
return row;
|
||||
}
|
||||
|
||||
function createProgressTable(
|
||||
progressData: ProgressData,
|
||||
euleriansPlace: string,
|
||||
locationUrl: string,
|
||||
languageUrl: string,
|
||||
accountData: AccountData,
|
||||
locationRating: RatingData,
|
||||
languageRating: RatingData
|
||||
): HTMLElement {
|
||||
const table = document.createElement('table');
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
['Competition', 'Status', 'Progress bar'].forEach(headerText => {
|
||||
const headerCell = document.createElement('th');
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild(headerCell);
|
||||
});
|
||||
tableBody.appendChild(headerRow);
|
||||
|
||||
// Solved out of total
|
||||
const progressPercentage = createProgressBar(progressData.percentage);
|
||||
tableBody.appendChild(createTableRow(
|
||||
`Progress`,
|
||||
progressData.progress,
|
||||
progressPercentage
|
||||
));
|
||||
|
||||
// Eulerians' place
|
||||
const euleriansLink = document.createElement('a');
|
||||
euleriansLink.href = 'https://projecteuler.net/eulerians';
|
||||
euleriansLink.textContent = 'Place in Eulerians';
|
||||
tableBody.appendChild(createTableRow(
|
||||
euleriansLink,
|
||||
euleriansPlace
|
||||
));
|
||||
|
||||
// Location rating
|
||||
const locationPlace = locationRating.place > 100 ? 'You are not in the Top 100' : locationRating.place.toString();
|
||||
const locationPlaceToTop = locationRating.place > 100 ? 0 : 100 - locationRating.place;
|
||||
const locationPercentage = createProgressBar(locationPlaceToTop);
|
||||
const locationLink = document.createElement('a');
|
||||
locationLink.href = locationUrl;
|
||||
locationLink.textContent = `Place in ${accountData.location}`;
|
||||
tableBody.appendChild(createTableRow(
|
||||
locationLink,
|
||||
locationPlace,
|
||||
locationPercentage
|
||||
));
|
||||
|
||||
// Language rating
|
||||
const languagePlace = languageRating.place > 100 ? 'You are not in the Top 100' : languageRating.place.toString();
|
||||
const languagePlaceToTop = languageRating.place > 100 ? 0 : 100 - languageRating.place;
|
||||
const languagePercentage = createProgressBar(languagePlaceToTop);
|
||||
const languageLink = document.createElement('a');
|
||||
languageLink.href = languageUrl;
|
||||
languageLink.textContent = `Place in ${accountData.language}`;
|
||||
tableBody.appendChild(createTableRow(
|
||||
languageLink,
|
||||
languagePlace,
|
||||
languagePercentage
|
||||
));
|
||||
|
||||
table.appendChild(tableBody);
|
||||
return table;
|
||||
}
|
||||
|
||||
function createTasksList(
|
||||
progressData: ProgressData,
|
||||
accountData: AccountData,
|
||||
locationRating: RatingData,
|
||||
languageRating: RatingData,
|
||||
awardsData: AwardBlockData[]
|
||||
): HTMLElement {
|
||||
const tasksList = document.createElement('ul');
|
||||
tasksList.className = 'tasks-list';
|
||||
|
||||
// Progress to the next level
|
||||
const matchToTheNext = progressData.toTheNext.match(/(\d+)/);
|
||||
const countToTheNext = matchToTheNext ? (25 - parseInt(matchToTheNext[1], 10)) * 4 : 0;
|
||||
const toTheNextPercentage = createProgressBar(countToTheNext);
|
||||
const levelTask = document.createElement('li');
|
||||
levelTask.textContent = `${progressData.level}: ${progressData.toTheNext}. `;
|
||||
levelTask.appendChild(toTheNextPercentage);
|
||||
tasksList.appendChild(levelTask);
|
||||
|
||||
// Location progress
|
||||
const locationResult = determineTop(progressData.solved, locationRating);
|
||||
const locationLi = document.createElement('li');
|
||||
locationLi.textContent = `${accountData.location}: ${locationResult.message}. `;
|
||||
locationLi.appendChild(createProgressBar(locationResult.percentage));
|
||||
tasksList.appendChild(locationLi);
|
||||
|
||||
// Language progress
|
||||
const languageResult = determineTop(progressData.solved, languageRating);
|
||||
const languageLi = document.createElement('li');
|
||||
languageLi.textContent = `${accountData.language}: ${languageResult.message}. `;
|
||||
languageLi.appendChild(createProgressBar(languageResult.percentage));
|
||||
tasksList.appendChild(languageLi);
|
||||
|
||||
// Award progress
|
||||
const award = findAwardWithMaxMembers(awardsData);
|
||||
if (award) {
|
||||
const awardLi = document.createElement('li');
|
||||
const awardLink = document.createElement('a');
|
||||
awardLink.href = award.link;
|
||||
awardLink.textContent = award.award;
|
||||
awardLi.textContent = `Most unresolved award: `;
|
||||
awardLi.appendChild(awardLink);
|
||||
awardLi.append(` (${award.description}) by ${award.members}. `);
|
||||
awardLi.appendChild(createProgressBar(award.percentage));
|
||||
tasksList.appendChild(awardLi);
|
||||
}
|
||||
|
||||
return tasksList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the progress table.
|
||||
* @param accountData - The account data.
|
||||
* @param progressData - The progress data.
|
||||
* @param locationUrl - The URL for the location rating.
|
||||
* @param languageUrl - The URL for the language rating.
|
||||
* @param euleriansPlace - The user's place in Eulerians' rating.
|
||||
* @param locationRating - The location rating data.
|
||||
* @param languageRating - The language rating data.
|
||||
* @param awardsData - The awards data.
|
||||
* @returns The generated HTMLElement (progress table container).
|
||||
*/
|
||||
export function generateProgressTableHTML(
|
||||
accountData: AccountData,
|
||||
progressData: ProgressData,
|
||||
locationUrl: string,
|
||||
languageUrl: string,
|
||||
euleriansPlace: string,
|
||||
locationRating: RatingData,
|
||||
languageRating: RatingData,
|
||||
awardsData: AwardBlockData[]
|
||||
): HTMLElement {
|
||||
const progressContainer = document.createElement('div');
|
||||
progressContainer.appendChild(createSectionHeader('Progress'));
|
||||
progressContainer.appendChild(createProgressBar(progressData.percentage));
|
||||
progressContainer.appendChild(createProgressTable(
|
||||
progressData,
|
||||
euleriansPlace,
|
||||
locationUrl,
|
||||
languageUrl,
|
||||
accountData,
|
||||
locationRating,
|
||||
languageRating
|
||||
));
|
||||
|
||||
// Tasks section
|
||||
const tasksHeader = document.createElement('h3');
|
||||
tasksHeader.textContent = 'Tasks';
|
||||
progressContainer.appendChild(tasksHeader);
|
||||
progressContainer.appendChild(createTasksList(
|
||||
progressData,
|
||||
accountData,
|
||||
locationRating,
|
||||
languageRating,
|
||||
awardsData
|
||||
));
|
||||
|
||||
return progressContainer;
|
||||
}
|
||||
82
helpers/ratingBlock.ts
Normal file
82
helpers/ratingBlock.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { RatingData } from './types';
|
||||
import { createSectionHeader } from './commonBlocks';
|
||||
|
||||
function createTableRow(label: string, solvedCount: number, remaining: number): HTMLElement {
|
||||
const row = document.createElement('tr');
|
||||
const cell1 = document.createElement('td');
|
||||
const cell2 = document.createElement('td');
|
||||
const cell3 = document.createElement('td');
|
||||
|
||||
cell1.textContent = label;
|
||||
cell2.textContent = solvedCount.toString();
|
||||
cell3.textContent = `${remaining} problems away from ${label}`;
|
||||
|
||||
row.appendChild(cell1);
|
||||
row.appendChild(cell2);
|
||||
row.appendChild(cell3);
|
||||
return row;
|
||||
}
|
||||
|
||||
function createRatingTable(solved: number, rating: RatingData): HTMLElement {
|
||||
const table = document.createElement('table');
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
const headerRow = document.createElement('tr');
|
||||
['Competition', 'Solved', 'Remaining'].forEach(headerText => {
|
||||
const headerCell = document.createElement('th');
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild(headerCell);
|
||||
});
|
||||
tableBody.appendChild(headerRow);
|
||||
|
||||
if (rating.place > 100) {
|
||||
tableBody.appendChild(createTableRow('Top 100', rating.top100, rating.top100 - solved + 1));
|
||||
}
|
||||
if (rating.place > 50) {
|
||||
tableBody.appendChild(createTableRow('Top 50', rating.top50, rating.top50 - solved + 1));
|
||||
}
|
||||
if (rating.place > 25) {
|
||||
tableBody.appendChild(createTableRow('Top 25', rating.top25, rating.top25 - solved + 1));
|
||||
}
|
||||
if (rating.place > 10) {
|
||||
tableBody.appendChild(createTableRow('Top 10', rating.top10, rating.top10 - solved + 1));
|
||||
}
|
||||
if (rating.place > 5) {
|
||||
tableBody.appendChild(createTableRow('Top 5', rating.top5, rating.top5 - solved + 1));
|
||||
}
|
||||
if (rating.place >= 1) {
|
||||
tableBody.appendChild(createTableRow('Top 1', rating.top1, rating.top1 - solved + 1));
|
||||
}
|
||||
|
||||
table.appendChild(tableBody);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for the rating table.
|
||||
* @param title - The title of the rating table.
|
||||
* @param solved - The number of solved problems.
|
||||
* @param rating - The rating data.
|
||||
* @returns The generated HTMLElement (rating table container).
|
||||
*/
|
||||
export function generateRatingTableHTML(url: string, title: string, solved: number, rating: RatingData): HTMLElement {
|
||||
const ratingContainer = document.createElement('div');
|
||||
|
||||
// Header with link
|
||||
const header = document.createElement('h2');
|
||||
const textNode = document.createTextNode(`Progress in `);
|
||||
header.appendChild(textNode);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.textContent = `the ${title}'s rating`;
|
||||
header.appendChild(link);
|
||||
ratingContainer.appendChild(header);
|
||||
|
||||
const place = rating.place > 100 ? 'You are not in the Top 100' : rating.place.toString();
|
||||
ratingContainer.appendChild(createSectionHeader(`Current place: ${place}`, 'h4'));
|
||||
ratingContainer.appendChild(createSectionHeader(`Solved problems: ${solved}`, 'h5'));
|
||||
ratingContainer.appendChild(createRatingTable(solved, rating));
|
||||
|
||||
return ratingContainer;
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +66,14 @@ export class AwardBlockData {
|
|||
public name: string,
|
||||
public awards: AwardData[]
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Calculates the number of completed awards in this block.
|
||||
* @returns The number of completed awards.
|
||||
*/
|
||||
getCompletedCount(): number {
|
||||
return this.awards.filter(award => award.isCompleted).length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
11
main.ts
11
main.ts
|
|
@ -9,20 +9,13 @@ export default class ProjectEulerStatsPlugin extends Plugin {
|
|||
|
||||
this.addSettingTab(new ProjectEulerStatsSettingTab(this.app, this));
|
||||
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
|
||||
const session = this.settings.session_id;
|
||||
const keep_alive = this.settings.keep_alive;
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor('euler-stats', async (source, el, ctx) => {
|
||||
this.registerMarkdownCodeBlockProcessor('euler-stats', async (source, el, ctx) => {
|
||||
const stats = await fetchProgress(session, keep_alive);
|
||||
|
||||
const container = el.createEl('div');
|
||||
container.innerHTML = stats;
|
||||
container.appendChild(stats);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "project-euler-stats",
|
||||
"name": "Project Euler Stats",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Plugin for getting statistics from ProjectEuler.",
|
||||
"author": "Artem Korsakov",
|
||||
|
|
|
|||
Loading…
Reference in a new issue