2025-03-11 06:47:13 +00:00
|
|
|
|
import { stringToNumber } from './parsers';
|
2025-03-13 06:26:43 +00:00
|
|
|
|
import { AccountData, AwardData, AwardBlockData, FriendData, ProgressData, RatingData, LevelData } from './types';
|
2025-03-11 05:46:48 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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>
|
2025-03-13 08:16:30 +00:00
|
|
|
|
<table class="profile-table">
|
2025-03-11 05:46:48 +00:00
|
|
|
|
<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}">`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 06:37:04 +00:00
|
|
|
|
interface Result {
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
percentage: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function determineTop(solved: number, rating: RatingData): Result {
|
2025-03-13 06:26:43 +00:00
|
|
|
|
const thresholds = [
|
2025-03-14 06:37:04 +00:00
|
|
|
|
{ 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' },
|
2025-03-13 06:26:43 +00:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-03-14 06:37:04 +00:00
|
|
|
|
for (let i = 0; i < thresholds.length; i++) {
|
|
|
|
|
|
const { limit, top, title } = thresholds[i];
|
2025-03-13 06:26:43 +00:00
|
|
|
|
if (rating.place > limit) {
|
2025-03-14 06:37:04 +00:00
|
|
|
|
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 };
|
2025-03-13 06:26:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 06:37:04 +00:00
|
|
|
|
return { message: 'You are in the Top 1', percentage: 100 };
|
2025-03-13 06:26:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Функция для нахождения 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-14 06:37:04 +00:00
|
|
|
|
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>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-11 05:46:48 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 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,
|
2025-03-13 06:26:43 +00:00
|
|
|
|
languageRating: RatingData,
|
|
|
|
|
|
awardsData: AwardBlockData[]
|
2025-03-11 05:46:48 +00:00
|
|
|
|
): 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;
|
2025-03-14 06:37:04 +00:00
|
|
|
|
|
|
|
|
|
|
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>`;
|
|
|
|
|
|
|
2025-03-13 06:26:43 +00:00
|
|
|
|
const award = findAwardWithMaxMembers(awardsData);
|
2025-03-14 06:37:04 +00:00
|
|
|
|
const awardPlace = award ? `<li>Most unresolved award: <a href="${award.link}">${award.award}</a> (${award.description}) by ${award.members}. ${createProgressBar(award.percentage)}</li>` : '';
|
2025-03-11 05:46:48 +00:00
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
<h2>Progress</h2>
|
2025-03-14 06:37:04 +00:00
|
|
|
|
${progressPercentage}
|
2025-03-11 05:46:48 +00:00
|
|
|
|
<table>
|
|
|
|
|
|
<tbody>
|
2025-03-14 06:37:04 +00:00
|
|
|
|
<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>
|
2025-03-11 05:46:48 +00:00
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
2025-03-13 06:26:43 +00:00
|
|
|
|
<h3>Tasks</h3>
|
2025-03-13 08:16:30 +00:00
|
|
|
|
<ul class="tasks-list">
|
2025-03-14 06:37:04 +00:00
|
|
|
|
<li>${progressData.level}: ${progressData.toTheNext}. ${toTheNextPercentage}</li>
|
|
|
|
|
|
${locationLiElement}
|
|
|
|
|
|
${languageLiElement}
|
2025-03-13 06:26:43 +00:00
|
|
|
|
${awardPlace}
|
|
|
|
|
|
</ul>
|
2025-03-11 05:46:48 +00:00
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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>` : '';
|
2025-03-13 06:26:43 +00:00
|
|
|
|
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>` : '';
|
2025-03-11 05:46:48 +00:00
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
2025-03-11 06:47:13 +00:00
|
|
|
|
|
|
|
|
|
|
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 `
|
2025-03-13 08:16:30 +00:00
|
|
|
|
<tr">
|
2025-03-11 06:47:13 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
2025-03-12 07:13:30 +00:00
|
|
|
|
export function generateAwardsTableHTML(awardsData: AwardBlockData[]): string {
|
|
|
|
|
|
const tables = awardsData.map(awardData => {
|
|
|
|
|
|
const awards = awardData.awards;
|
|
|
|
|
|
const length = awards.length;
|
|
|
|
|
|
|
|
|
|
|
|
// Подсчитываем завершенные и создаем строки таблицы за один проход
|
2025-03-15 09:22:05 +00:00
|
|
|
|
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: [] } // Начальное значение аккумулятора
|
|
|
|
|
|
);
|
2025-03-12 07:13:30 +00:00
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
<h3>${awardData.name}</h3>
|
|
|
|
|
|
<h4>Status: Won ${completed} out of ${length}</h4>
|
|
|
|
|
|
<h4>Uncompleted awards: ${length - completed}</h4>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<tbody>
|
2025-03-14 06:37:04 +00:00
|
|
|
|
<tr><th>Award</th><th>Description</th><th>Members</th><th>Progress</th><th>Progress bar</th></tr>
|
2025-03-12 07:13:30 +00:00
|
|
|
|
${rows.join('')}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
<h2>Awards</h2>
|
|
|
|
|
|
${tables}
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
2025-03-11 06:47:13 +00:00
|
|
|
|
|
2025-03-13 06:26:43 +00:00
|
|
|
|
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 => {
|
2025-03-13 08:16:30 +00:00
|
|
|
|
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;
|
2025-03-13 06:26:43 +00:00
|
|
|
|
|
|
|
|
|
|
return `
|
2025-03-13 08:16:30 +00:00
|
|
|
|
<tr ${idCss}>
|
|
|
|
|
|
<td>${friend.rank}</td>
|
2025-03-13 06:26:43 +00:00
|
|
|
|
<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>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|