Add compact view

This commit is contained in:
Artem Korsakov 2025-03-28 09:05:39 +03:00
parent 656dd7b506
commit a2c77e59d5
8 changed files with 155 additions and 88 deletions

View file

@ -30,7 +30,7 @@ function createAwardTableRow(award: AwardData): HTMLElement {
return row; return row;
} }
function createAwardTable(awardData: AwardBlockData): HTMLElement { function createAwardTable(awardData: AwardBlockData, useShortFormat: boolean): HTMLElement {
const table = document.createElement('table'); const table = document.createElement('table');
const tableBody = document.createElement('tbody'); const tableBody = document.createElement('tbody');
@ -42,17 +42,30 @@ function createAwardTable(awardData: AwardBlockData): HTMLElement {
}); });
tableBody.appendChild(headerRow); tableBody.appendChild(headerRow);
awardData.awards.forEach(award => { const sortedAwards =
if (!award.isCompleted) { awardData.awards
.filter(award => !award.isCompleted)
.map(award => ({
award,
members: parseInt(award.members.replace(/\D/g, '')) || 0
}))
.sort((a, b) => b.members - a.members)
if (useShortFormat) {
sortedAwards.slice(0, 3).forEach(({ award }) => {
tableBody.appendChild(createAwardTableRow(award)); tableBody.appendChild(createAwardTableRow(award));
} });
}); } else {
sortedAwards.forEach(({ award }) => {
tableBody.appendChild(createAwardTableRow(award));
});
}
table.appendChild(tableBody); table.appendChild(tableBody);
return table; return table;
} }
function createAwardBlock(awardData: AwardBlockData): HTMLElement { function createAwardBlock(awardData: AwardBlockData, useShortFormat: boolean): HTMLElement {
const awardBlock = document.createElement('div'); const awardBlock = document.createElement('div');
const awardName = document.createElement('h3'); const awardName = document.createElement('h3');
@ -62,15 +75,17 @@ function createAwardBlock(awardData: AwardBlockData): HTMLElement {
const completed = awardData.getCompletedCount(); const completed = awardData.getCompletedCount();
const total = awardData.awards.length; const total = awardData.awards.length;
const completedStatus = document.createElement('h4'); if (!useShortFormat) {
completedStatus.textContent = `Status: Won ${completed} out of ${total}`; const completedStatus = document.createElement('h4');
awardBlock.appendChild(completedStatus); completedStatus.textContent = `Status: Won ${completed} out of ${total}`;
awardBlock.appendChild(completedStatus);
const uncompletedStatus = document.createElement('h4'); const uncompletedStatus = document.createElement('h4');
uncompletedStatus.textContent = `Uncompleted awards: ${total - completed}`; uncompletedStatus.textContent = `Uncompleted awards: ${total - completed}`;
awardBlock.appendChild(uncompletedStatus); awardBlock.appendChild(uncompletedStatus);
}
awardBlock.appendChild(createAwardTable(awardData)); awardBlock.appendChild(createAwardTable(awardData, useShortFormat));
return awardBlock; return awardBlock;
} }
@ -80,7 +95,7 @@ function createAwardBlock(awardData: AwardBlockData): HTMLElement {
* @param awardsData - The awards data. * @param awardsData - The awards data.
* @returns The generated HTMLElement (awards table container). * @returns The generated HTMLElement (awards table container).
*/ */
export function generateAwardsTableHTML(awardsData: AwardBlockData[]): HTMLElement { export function generateAwardsTableHTML(awardsData: AwardBlockData[], useShortFormat: boolean): HTMLElement {
const awardsContainer = document.createElement('div'); const awardsContainer = document.createElement('div');
const awardsHeader = document.createElement('h2'); const awardsHeader = document.createElement('h2');
@ -88,7 +103,7 @@ export function generateAwardsTableHTML(awardsData: AwardBlockData[]): HTMLEleme
awardsContainer.appendChild(awardsHeader); awardsContainer.appendChild(awardsHeader);
awardsData.forEach(awardData => { awardsData.forEach(awardData => {
awardsContainer.appendChild(createAwardBlock(awardData)); awardsContainer.appendChild(createAwardBlock(awardData, useShortFormat));
}); });
return awardsContainer; return awardsContainer;

View file

@ -39,14 +39,14 @@ const keepAliveName = 'keep_alive';
* @param source - The source data for generating HTML. * @param source - The source data for generating HTML.
* @returns A Promise that resolves to an HTMLElement. * @returns A Promise that resolves to an HTMLElement.
*/ */
export async function fetchProgress(session: string, keep_alive: string, source: Source): Promise<HTMLElement> { export async function fetchProgress(session: string, keep_alive: string, source: Source, useShortFormat: boolean): Promise<HTMLElement> {
// Attempt to load cached data from the file // Attempt to load cached data from the file
const filePath = await getFilePath(); const filePath = await getFilePath();
const savedData = await loadDataFromFile(filePath); const savedData = await loadDataFromFile(filePath);
// If cached data exists, generate and return HTML from it // If cached data exists, generate and return HTML from it
if (savedData) { if (savedData) {
return generateHTML(savedData, source); return generateHTML(savedData, source, useShortFormat);
} }
// If no cached data is available, try to fetch fresh data from the server // If no cached data is available, try to fetch fresh data from the server
@ -54,7 +54,7 @@ export async function fetchProgress(session: string, keep_alive: string, source:
// If fetching fresh data is successful, generate and return HTML from it // If fetching fresh data is successful, generate and return HTML from it
if (fetchedData) { if (fetchedData) {
return generateHTML(fetchedData, source); return generateHTML(fetchedData, source, useShortFormat);
} }
// If neither cached nor fresh data is available, create an error message // If neither cached nor fresh data is available, create an error message
@ -137,34 +137,36 @@ async function fetchAwardsData(cookies: string): Promise<AwardBlockData[]> {
return parseAwardsData(myAwardsHtml, awardsHtml); return parseAwardsData(myAwardsHtml, awardsHtml);
} }
function generateHTML(cache: CacheData, source: Source): HTMLElement { function generateHTML(cache: CacheData, source: Source, useShortFormat: boolean): HTMLElement {
const container = document.createElement('div'); const container = document.createElement('div');
const profileElement = generateProfileHTML(cache.accountData, cache.progressData); const profileElement = generateProfileHTML(cache.accountData, cache.progressData, useShortFormat);
container.appendChild(profileElement); container.appendChild(profileElement);
const imageElement = generateImageHTML(cache.accountData.account); if (!useShortFormat) {
container.appendChild(imageElement); const imageElement = generateImageHTML(cache.accountData.account);
container.appendChild(imageElement);
}
const progressElement = generateProgressTableHTML(cache); const progressElement = generateProgressTableHTML(cache, useShortFormat);
container.appendChild(progressElement); container.appendChild(progressElement);
const tasksElement = generateTasksTableHTML(cache, source); const tasksElement = generateTasksTableHTML(cache, source);
container.appendChild(tasksElement); container.appendChild(tasksElement);
const locationRatingElement = generateRatingTableHTML(cache.locationUrl, cache.accountData.location, cache.progressData.solved, cache.locationRating); const locationRatingElement = generateRatingTableHTML(cache.locationUrl, cache.accountData.location, cache.progressData.solved, cache.locationRating, useShortFormat);
container.appendChild(locationRatingElement); container.appendChild(locationRatingElement);
const languageRatingElement = generateRatingTableHTML(cache.languageUrl, cache.accountData.language, cache.progressData.solved, cache.languageRating); const languageRatingElement = generateRatingTableHTML(cache.languageUrl, cache.accountData.language, cache.progressData.solved, cache.languageRating, useShortFormat);
container.appendChild(languageRatingElement); container.appendChild(languageRatingElement);
const levelsElement = generateLevelsTableHTML(cache.progressData, cache.levelDataArray); const levelsElement = generateLevelsTableHTML(cache.progressData, cache.levelDataArray, useShortFormat);
container.appendChild(levelsElement); container.appendChild(levelsElement);
const awardsElement = generateAwardsTableHTML(cache.awardsData); const awardsElement = generateAwardsTableHTML(cache.awardsData, useShortFormat);
container.appendChild(awardsElement); container.appendChild(awardsElement);
const friendsElement = generateFriendsHTML(cache.friends, cache.accountData); const friendsElement = generateFriendsHTML(cache.friends, cache.accountData, useShortFormat);
container.appendChild(friendsElement); container.appendChild(friendsElement);
return container; return container;

View file

@ -52,7 +52,7 @@ function createFriendTableRow(friend: FriendData, myAccount: FriendData, account
return row; return row;
} }
function createFriendsTable(friends: FriendData[], myAccount: FriendData, accountData: AccountData): HTMLElement { function createFriendsTable(friends: FriendData[], myAccount: FriendData, accountData: AccountData, useShortFormat: boolean): HTMLElement {
const table = document.createElement('table'); const table = document.createElement('table');
const tableBody = document.createElement('tbody'); const tableBody = document.createElement('tbody');
@ -64,9 +64,15 @@ function createFriendsTable(friends: FriendData[], myAccount: FriendData, accoun
}); });
tableBody.appendChild(headerRow); tableBody.appendChild(headerRow);
friends.forEach(friend => { if (useShortFormat) {
tableBody.appendChild(createFriendTableRow(friend, myAccount, accountData)); friends.slice(0, 3).forEach(friend => {
}); tableBody.appendChild(createFriendTableRow(friend, myAccount, accountData));
});
} else {
friends.forEach(friend => {
tableBody.appendChild(createFriendTableRow(friend, myAccount, accountData));
});
}
table.appendChild(tableBody); table.appendChild(tableBody);
return table; return table;
@ -78,11 +84,11 @@ function createFriendsTable(friends: FriendData[], myAccount: FriendData, accoun
* @param accountData - The account data. * @param accountData - The account data.
* @returns The generated HTMLElement (friends table container). * @returns The generated HTMLElement (friends table container).
*/ */
export function generateFriendsHTML(friends: FriendData[], accountData: AccountData): HTMLElement { export function generateFriendsHTML(friends: FriendData[], accountData: AccountData, useShortFormat: boolean): HTMLElement {
const friendsContainer = document.createElement('div'); const friendsContainer = document.createElement('div');
friendsContainer.appendChild(createSectionHeader('Friends')); friendsContainer.appendChild(createSectionHeader('Friends'));
const myAccount = friends.find(friend => friend.username === accountData.alias) ?? new FriendData('-', accountData.alias, 0, 0, 0); const myAccount = friends.find(friend => friend.username === accountData.alias) ?? new FriendData('-', accountData.alias, 0, 0, 0);
friendsContainer.appendChild(createFriendsTable(friends, myAccount, accountData)); friendsContainer.appendChild(createFriendsTable(friends, myAccount, accountData, useShortFormat));
return friendsContainer; return friendsContainer;
} }

View file

@ -29,7 +29,7 @@ function createLevelTableRow(levelData: LevelData, progressData: ProgressData):
return row; return row;
} }
function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData[]): HTMLElement { function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData[], useShortFormat: boolean): HTMLElement {
const table = document.createElement('table'); const table = document.createElement('table');
const tableBody = document.createElement('tbody'); const tableBody = document.createElement('tbody');
@ -41,9 +41,15 @@ function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData
}); });
tableBody.appendChild(headerRow); tableBody.appendChild(headerRow);
levelDataArray.forEach(levelData => { if (useShortFormat) {
tableBody.appendChild(createLevelTableRow(levelData, progressData)); levelDataArray.slice(0, 3).forEach(levelData => {
}); tableBody.appendChild(createLevelTableRow(levelData, progressData));
});
} else {
levelDataArray.forEach(levelData => {
tableBody.appendChild(createLevelTableRow(levelData, progressData));
});
}
table.appendChild(tableBody); table.appendChild(tableBody);
return table; return table;
@ -62,7 +68,7 @@ function calculateTotalMembers(levelDataArray: LevelData[]): number {
* @param levelDataArray - The array of level data. * @param levelDataArray - The array of level data.
* @returns The generated HTMLElement (levels table container). * @returns The generated HTMLElement (levels table container).
*/ */
export function generateLevelsTableHTML(progressData: ProgressData, levelDataArray: LevelData[]): HTMLElement { export function generateLevelsTableHTML(progressData: ProgressData, levelDataArray: LevelData[], useShortFormat: boolean): HTMLElement {
const level = stringToNumber(progressData.level); const level = stringToNumber(progressData.level);
const levelsContainer = document.createElement('div'); const levelsContainer = document.createElement('div');
@ -75,12 +81,15 @@ export function generateLevelsTableHTML(progressData: ProgressData, levelDataArr
const slicedArray = levelDataArray.slice(level - 1); const slicedArray = levelDataArray.slice(level - 1);
levelsContainer.appendChild(createSectionHeader('Level progress')); 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)); if (!useShortFormat) {
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, useShortFormat));
return levelsContainer; return levelsContainer;
} }

View file

@ -6,13 +6,40 @@ import { AccountData, ProgressData } from './types';
* @param progressData - The progress data. * @param progressData - The progress data.
* @returns The generated HTMLElement. * @returns The generated HTMLElement.
*/ */
export function generateProfileHTML(accountData: AccountData, progressData: ProgressData): HTMLElement { export function generateProfileHTML(accountData: AccountData, progressData: ProgressData, useShortFormat: boolean): HTMLElement {
const profileContainer = document.createElement('div'); const profileContainer = document.createElement('div');
const profileHeader = document.createElement('h2'); const profileHeader = document.createElement('h2');
profileHeader.textContent = 'Profile'; profileHeader.textContent = 'Profile';
profileContainer.appendChild(profileHeader); profileContainer.appendChild(profileHeader);
if (useShortFormat) {
const profileTable = generateCompactProfileBlock(accountData, progressData);
profileContainer.appendChild(profileTable);
} else {
const profileTable = generateProfileTable(accountData, progressData);
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;
}
function generateProfileTable(accountData: AccountData, progressData: ProgressData): HTMLElement {
const profileTable = document.createElement('table'); const profileTable = document.createElement('table');
profileTable.className = 'profile-table'; profileTable.className = 'profile-table';
@ -39,22 +66,12 @@ export function generateProfileHTML(accountData: AccountData, progressData: Prog
addRow('Solved', progressData.solved); addRow('Solved', progressData.solved);
profileTable.appendChild(tableBody); profileTable.appendChild(tableBody);
profileContainer.appendChild(profileTable);
return profileContainer; return profileTable;
} }
/** function generateCompactProfileBlock(accountData: AccountData, progressData: ProgressData): HTMLElement {
* Generates HTML for the profile image. const profileBlock = document.createElement('div');
* @param account - The account name. profileBlock.textContent = `${accountData.alias} (${accountData.account}), ${accountData.location}, ${accountData.language}, ${progressData.level}, ${progressData.solved} solved`;
* @returns The generated HTMLElement (img element). return profileBlock;
*/
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;
} }

View file

@ -99,10 +99,12 @@ function createProgressTable(
* @param cache - The cache data. * @param cache - The cache data.
* @returns The generated HTMLElement (progress table container). * @returns The generated HTMLElement (progress table container).
*/ */
export function generateProgressTableHTML(cache: CacheData): HTMLElement { export function generateProgressTableHTML(cache: CacheData, useShortFormat: boolean): HTMLElement {
const progressContainer = document.createElement('div'); const progressContainer = document.createElement('div');
progressContainer.appendChild(createSectionHeader('Progress')); progressContainer.appendChild(createSectionHeader('Progress'));
progressContainer.appendChild(createProgressBar(cache.progressData.percentage)); if (!useShortFormat) {
progressContainer.appendChild(createProgressBar(cache.progressData.percentage));
}
progressContainer.appendChild(createProgressTable( progressContainer.appendChild(createProgressTable(
cache.progressData, cache.progressData,
cache.euleriansPlace, cache.euleriansPlace,

View file

@ -17,10 +17,11 @@ function createTableRow(label: string, solvedCount: number, remaining: number):
return row; return row;
} }
function createRatingTable(solved: number, rating: RatingData): HTMLElement { function createRatingTable(solved: number, rating: RatingData, useShortFormat: boolean): HTMLElement {
const table = document.createElement('table'); const table = document.createElement('table');
const tableBody = document.createElement('tbody'); const tableBody = document.createElement('tbody');
// Create table header
const headerRow = document.createElement('tr'); const headerRow = document.createElement('tr');
['Competition', 'Solved', 'Remaining'].forEach(headerText => { ['Competition', 'Solved', 'Remaining'].forEach(headerText => {
const headerCell = document.createElement('th'); const headerCell = document.createElement('th');
@ -29,24 +30,25 @@ function createRatingTable(solved: number, rating: RatingData): HTMLElement {
}); });
tableBody.appendChild(headerRow); tableBody.appendChild(headerRow);
if (rating.place > 100) { // Define rating tiers with their display conditions
tableBody.appendChild(createTableRow('Top 100', rating.top100, rating.top100 - solved + 1)); const ratingTiers = [
} { name: 'Top 100', value: rating.top100, showCondition: rating.place > 100 },
if (rating.place > 50) { { name: 'Top 50', value: rating.top50, showCondition: rating.place > 50 },
tableBody.appendChild(createTableRow('Top 50', rating.top50, rating.top50 - solved + 1)); { name: 'Top 25', value: rating.top25, showCondition: rating.place > 25 },
} { name: 'Top 10', value: rating.top10, showCondition: rating.place > 10 },
if (rating.place > 25) { { name: 'Top 5', value: rating.top5, showCondition: rating.place > 5 },
tableBody.appendChild(createTableRow('Top 25', rating.top25, rating.top25 - solved + 1)); { name: 'Top 1', value: rating.top1, showCondition: rating.place >= 1 }
} ];
if (rating.place > 10) {
tableBody.appendChild(createTableRow('Top 10', rating.top10, rating.top10 - solved + 1)); // Filter tiers based on current position and format preference
} const visibleTiers = ratingTiers.filter(tier => tier.showCondition);
if (rating.place > 5) { const tiersToShow = useShortFormat ? visibleTiers.slice(0, 3) : visibleTiers;
tableBody.appendChild(createTableRow('Top 5', rating.top5, rating.top5 - solved + 1));
} // Add rows for each visible tier
if (rating.place >= 1) { tiersToShow.forEach(tier => {
tableBody.appendChild(createTableRow('Top 1', rating.top1, rating.top1 - solved + 1)); const remaining = tier.value - solved + 1;
} tableBody.appendChild(createTableRow(tier.name, tier.value, remaining));
});
table.appendChild(tableBody); table.appendChild(tableBody);
return table; return table;
@ -59,7 +61,7 @@ function createRatingTable(solved: number, rating: RatingData): HTMLElement {
* @param rating - The rating data. * @param rating - The rating data.
* @returns The generated HTMLElement (rating table container). * @returns The generated HTMLElement (rating table container).
*/ */
export function generateRatingTableHTML(url: string, title: string, solved: number, rating: RatingData): HTMLElement { export function generateRatingTableHTML(url: string, title: string, solved: number, rating: RatingData, useShortFormat: boolean): HTMLElement {
const ratingContainer = document.createElement('div'); const ratingContainer = document.createElement('div');
// Header with link // Header with link
@ -72,10 +74,12 @@ export function generateRatingTableHTML(url: string, title: string, solved: numb
header.appendChild(link); header.appendChild(link);
ratingContainer.appendChild(header); ratingContainer.appendChild(header);
const place = rating.place > 100 ? 'You are not in the Top 100' : rating.place.toString(); if (!useShortFormat) {
ratingContainer.appendChild(createSectionHeader(`Current place: ${place}`, 'h4')); const place = rating.place > 100 ? 'You are not in the Top 100' : rating.place.toString();
ratingContainer.appendChild(createSectionHeader(`Solved problems: ${solved}`, 'h5')); ratingContainer.appendChild(createSectionHeader(`Current place: ${place}`, 'h4'));
ratingContainer.appendChild(createRatingTable(solved, rating)); ratingContainer.appendChild(createSectionHeader(`Solved problems: ${solved}`, 'h5'));
}
ratingContainer.appendChild(createRatingTable(solved, rating, useShortFormat));
return ratingContainer; return ratingContainer;
} }

16
main.ts
View file

@ -12,7 +12,7 @@ export default class ProjectEulerStatsPlugin extends Plugin {
this.registerMarkdownCodeBlockProcessor('euler-stats', async (source, el, ctx) => { this.registerMarkdownCodeBlockProcessor('euler-stats', async (source, el, ctx) => {
const extractedSource = extractSources(source); const extractedSource = extractSources(source);
const stats = await fetchProgress(this.settings.session_id, this.settings.keep_alive, extractedSource); const stats = await fetchProgress(this.settings.session_id, this.settings.keep_alive, extractedSource, this.settings.use_short_format);
const container = el.createEl('div'); const container = el.createEl('div');
container.appendChild(stats); container.appendChild(stats);
}); });
@ -49,11 +49,13 @@ export default class ProjectEulerStatsPlugin extends Plugin {
export interface ProjectEulerStatsSettings { export interface ProjectEulerStatsSettings {
session_id: string; session_id: string;
keep_alive: string; keep_alive: string;
use_short_format: boolean;
} }
export const DEFAULT_SETTINGS: ProjectEulerStatsSettings = { export const DEFAULT_SETTINGS: ProjectEulerStatsSettings = {
session_id: '', session_id: '',
keep_alive: '' keep_alive: '',
use_short_format: false
} }
export class ProjectEulerStatsSettingTab extends PluginSettingTab { export class ProjectEulerStatsSettingTab extends PluginSettingTab {
@ -90,5 +92,15 @@ export class ProjectEulerStatsSettingTab extends PluginSettingTab {
this.plugin.settings.keep_alive = value; this.plugin.settings.keep_alive = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
new Setting(containerEl)
.setName('Use compact format')
.setDesc('Enable to display data in a compact view')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.use_short_format)
.onChange(async (value) => {
this.plugin.settings.use_short_format = value;
await this.plugin.saveSettings();
}));
} }
} }