From a2c77e59d5200d2dc97bd5eb5f43b1e4c3c5d3fc Mon Sep 17 00:00:00 2001 From: Artem Korsakov Date: Fri, 28 Mar 2025 09:05:39 +0300 Subject: [PATCH] Add compact view --- helpers/awardsBlock.ts | 45 ++++++++++++++++++++++------------ helpers/fetchProgress.ts | 28 ++++++++++++---------- helpers/friendsBlock.ts | 18 +++++++++----- helpers/levelsBlock.ts | 29 ++++++++++++++-------- helpers/profileBlock.ts | 49 ++++++++++++++++++++++++------------- helpers/progressBlock.ts | 6 +++-- helpers/ratingBlock.ts | 52 +++++++++++++++++++++------------------- main.ts | 16 +++++++++++-- 8 files changed, 155 insertions(+), 88 deletions(-) diff --git a/helpers/awardsBlock.ts b/helpers/awardsBlock.ts index df2cee7..fcf5c76 100644 --- a/helpers/awardsBlock.ts +++ b/helpers/awardsBlock.ts @@ -30,7 +30,7 @@ function createAwardTableRow(award: AwardData): HTMLElement { return row; } -function createAwardTable(awardData: AwardBlockData): HTMLElement { +function createAwardTable(awardData: AwardBlockData, useShortFormat: boolean): HTMLElement { const table = document.createElement('table'); const tableBody = document.createElement('tbody'); @@ -42,17 +42,30 @@ function createAwardTable(awardData: AwardBlockData): HTMLElement { }); tableBody.appendChild(headerRow); - awardData.awards.forEach(award => { - if (!award.isCompleted) { + const sortedAwards = + 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)); - } - }); + }); + } else { + sortedAwards.forEach(({ award }) => { + tableBody.appendChild(createAwardTableRow(award)); + }); + } table.appendChild(tableBody); return table; } -function createAwardBlock(awardData: AwardBlockData): HTMLElement { +function createAwardBlock(awardData: AwardBlockData, useShortFormat: boolean): HTMLElement { const awardBlock = document.createElement('div'); const awardName = document.createElement('h3'); @@ -62,15 +75,17 @@ function createAwardBlock(awardData: AwardBlockData): HTMLElement { 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); + if (!useShortFormat) { + 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); + const uncompletedStatus = document.createElement('h4'); + uncompletedStatus.textContent = `Uncompleted awards: ${total - completed}`; + awardBlock.appendChild(uncompletedStatus); + } - awardBlock.appendChild(createAwardTable(awardData)); + awardBlock.appendChild(createAwardTable(awardData, useShortFormat)); return awardBlock; } @@ -80,7 +95,7 @@ function createAwardBlock(awardData: AwardBlockData): HTMLElement { * @param awardsData - The awards data. * @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 awardsHeader = document.createElement('h2'); @@ -88,7 +103,7 @@ export function generateAwardsTableHTML(awardsData: AwardBlockData[]): HTMLEleme awardsContainer.appendChild(awardsHeader); awardsData.forEach(awardData => { - awardsContainer.appendChild(createAwardBlock(awardData)); + awardsContainer.appendChild(createAwardBlock(awardData, useShortFormat)); }); return awardsContainer; diff --git a/helpers/fetchProgress.ts b/helpers/fetchProgress.ts index f7d8caa..4268464 100644 --- a/helpers/fetchProgress.ts +++ b/helpers/fetchProgress.ts @@ -39,14 +39,14 @@ const keepAliveName = 'keep_alive'; * @param source - The source data for generating HTML. * @returns A Promise that resolves to an HTMLElement. */ -export async function fetchProgress(session: string, keep_alive: string, source: Source): Promise { +export async function fetchProgress(session: string, keep_alive: string, source: Source, useShortFormat: boolean): Promise { // Attempt to load cached data from the file const filePath = await getFilePath(); const savedData = await loadDataFromFile(filePath); // If cached data exists, generate and return HTML from it 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 @@ -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 (fetchedData) { - return generateHTML(fetchedData, source); + return generateHTML(fetchedData, source, useShortFormat); } // If neither cached nor fresh data is available, create an error message @@ -137,34 +137,36 @@ async function fetchAwardsData(cookies: string): Promise { 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 profileElement = generateProfileHTML(cache.accountData, cache.progressData); + const profileElement = generateProfileHTML(cache.accountData, cache.progressData, useShortFormat); container.appendChild(profileElement); - const imageElement = generateImageHTML(cache.accountData.account); - container.appendChild(imageElement); + if (!useShortFormat) { + const imageElement = generateImageHTML(cache.accountData.account); + container.appendChild(imageElement); + } - const progressElement = generateProgressTableHTML(cache); + const progressElement = generateProgressTableHTML(cache, useShortFormat); container.appendChild(progressElement); const tasksElement = generateTasksTableHTML(cache, source); 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); - 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); - const levelsElement = generateLevelsTableHTML(cache.progressData, cache.levelDataArray); + const levelsElement = generateLevelsTableHTML(cache.progressData, cache.levelDataArray, useShortFormat); container.appendChild(levelsElement); - const awardsElement = generateAwardsTableHTML(cache.awardsData); + const awardsElement = generateAwardsTableHTML(cache.awardsData, useShortFormat); container.appendChild(awardsElement); - const friendsElement = generateFriendsHTML(cache.friends, cache.accountData); + const friendsElement = generateFriendsHTML(cache.friends, cache.accountData, useShortFormat); container.appendChild(friendsElement); return container; diff --git a/helpers/friendsBlock.ts b/helpers/friendsBlock.ts index dd02be4..a396a56 100644 --- a/helpers/friendsBlock.ts +++ b/helpers/friendsBlock.ts @@ -52,7 +52,7 @@ function createFriendTableRow(friend: FriendData, myAccount: FriendData, account 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 tableBody = document.createElement('tbody'); @@ -64,9 +64,15 @@ function createFriendsTable(friends: FriendData[], myAccount: FriendData, accoun }); tableBody.appendChild(headerRow); - friends.forEach(friend => { - tableBody.appendChild(createFriendTableRow(friend, myAccount, accountData)); - }); + if (useShortFormat) { + 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); return table; @@ -78,11 +84,11 @@ function createFriendsTable(friends: FriendData[], myAccount: FriendData, accoun * @param accountData - The account data. * @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'); 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)); + friendsContainer.appendChild(createFriendsTable(friends, myAccount, accountData, useShortFormat)); return friendsContainer; } diff --git a/helpers/levelsBlock.ts b/helpers/levelsBlock.ts index 9081150..8de785e 100644 --- a/helpers/levelsBlock.ts +++ b/helpers/levelsBlock.ts @@ -29,7 +29,7 @@ function createLevelTableRow(levelData: LevelData, progressData: ProgressData): return row; } -function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData[]): HTMLElement { +function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData[], useShortFormat: boolean): HTMLElement { const table = document.createElement('table'); const tableBody = document.createElement('tbody'); @@ -41,9 +41,15 @@ function createLevelsTable(progressData: ProgressData, levelDataArray: LevelData }); tableBody.appendChild(headerRow); - levelDataArray.forEach(levelData => { - tableBody.appendChild(createLevelTableRow(levelData, progressData)); - }); + if (useShortFormat) { + levelDataArray.slice(0, 3).forEach(levelData => { + tableBody.appendChild(createLevelTableRow(levelData, progressData)); + }); + } else { + levelDataArray.forEach(levelData => { + tableBody.appendChild(createLevelTableRow(levelData, progressData)); + }); + } table.appendChild(tableBody); return table; @@ -62,7 +68,7 @@ function calculateTotalMembers(levelDataArray: LevelData[]): number { * @param levelDataArray - The array of level data. * @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 levelsContainer = document.createElement('div'); @@ -75,12 +81,15 @@ export function generateLevelsTableHTML(progressData: ProgressData, levelDataArr 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)); + 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; } diff --git a/helpers/profileBlock.ts b/helpers/profileBlock.ts index eb8b210..ff97eb1 100644 --- a/helpers/profileBlock.ts +++ b/helpers/profileBlock.ts @@ -6,13 +6,40 @@ import { AccountData, ProgressData } from './types'; * @param progressData - The progress data. * @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 profileHeader = document.createElement('h2'); profileHeader.textContent = 'Profile'; 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'); profileTable.className = 'profile-table'; @@ -39,22 +66,12 @@ export function generateProfileHTML(accountData: AccountData, progressData: Prog addRow('Solved', progressData.solved); profileTable.appendChild(tableBody); - profileContainer.appendChild(profileTable); - return profileContainer; + return profileTable; } -/** - * 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 generateCompactProfileBlock(accountData: AccountData, progressData: ProgressData): HTMLElement { + const profileBlock = document.createElement('div'); + profileBlock.textContent = `${accountData.alias} (${accountData.account}), ${accountData.location}, ${accountData.language}, ${progressData.level}, ${progressData.solved} solved`; + return profileBlock; } diff --git a/helpers/progressBlock.ts b/helpers/progressBlock.ts index 9662285..e517ace 100644 --- a/helpers/progressBlock.ts +++ b/helpers/progressBlock.ts @@ -99,10 +99,12 @@ function createProgressTable( * @param cache - The cache data. * @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'); progressContainer.appendChild(createSectionHeader('Progress')); - progressContainer.appendChild(createProgressBar(cache.progressData.percentage)); + if (!useShortFormat) { + progressContainer.appendChild(createProgressBar(cache.progressData.percentage)); + } progressContainer.appendChild(createProgressTable( cache.progressData, cache.euleriansPlace, diff --git a/helpers/ratingBlock.ts b/helpers/ratingBlock.ts index adebb11..2b6c764 100644 --- a/helpers/ratingBlock.ts +++ b/helpers/ratingBlock.ts @@ -17,10 +17,11 @@ function createTableRow(label: string, solvedCount: number, remaining: number): return row; } -function createRatingTable(solved: number, rating: RatingData): HTMLElement { +function createRatingTable(solved: number, rating: RatingData, useShortFormat: boolean): HTMLElement { const table = document.createElement('table'); const tableBody = document.createElement('tbody'); + // Create table header const headerRow = document.createElement('tr'); ['Competition', 'Solved', 'Remaining'].forEach(headerText => { const headerCell = document.createElement('th'); @@ -29,24 +30,25 @@ function createRatingTable(solved: number, rating: RatingData): HTMLElement { }); 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)); - } + // Define rating tiers with their display conditions + const ratingTiers = [ + { name: 'Top 100', value: rating.top100, showCondition: rating.place > 100 }, + { name: 'Top 50', value: rating.top50, showCondition: rating.place > 50 }, + { name: 'Top 25', value: rating.top25, showCondition: rating.place > 25 }, + { name: 'Top 10', value: rating.top10, showCondition: rating.place > 10 }, + { name: 'Top 5', value: rating.top5, showCondition: rating.place > 5 }, + { name: 'Top 1', value: rating.top1, showCondition: rating.place >= 1 } + ]; + + // Filter tiers based on current position and format preference + const visibleTiers = ratingTiers.filter(tier => tier.showCondition); + const tiersToShow = useShortFormat ? visibleTiers.slice(0, 3) : visibleTiers; + + // Add rows for each visible tier + tiersToShow.forEach(tier => { + const remaining = tier.value - solved + 1; + tableBody.appendChild(createTableRow(tier.name, tier.value, remaining)); + }); table.appendChild(tableBody); return table; @@ -59,7 +61,7 @@ function createRatingTable(solved: number, rating: RatingData): HTMLElement { * @param rating - The rating data. * @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'); // Header with link @@ -72,10 +74,12 @@ export function generateRatingTableHTML(url: string, title: string, solved: numb 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)); + if (!useShortFormat) { + 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, useShortFormat)); return ratingContainer; } diff --git a/main.ts b/main.ts index f0c97f8..951e175 100644 --- a/main.ts +++ b/main.ts @@ -12,7 +12,7 @@ export default class ProjectEulerStatsPlugin extends Plugin { this.registerMarkdownCodeBlockProcessor('euler-stats', async (source, el, ctx) => { 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'); container.appendChild(stats); }); @@ -49,11 +49,13 @@ export default class ProjectEulerStatsPlugin extends Plugin { export interface ProjectEulerStatsSettings { session_id: string; keep_alive: string; + use_short_format: boolean; } export const DEFAULT_SETTINGS: ProjectEulerStatsSettings = { session_id: '', - keep_alive: '' + keep_alive: '', + use_short_format: false } export class ProjectEulerStatsSettingTab extends PluginSettingTab { @@ -90,5 +92,15 @@ export class ProjectEulerStatsSettingTab extends PluginSettingTab { this.plugin.settings.keep_alive = value; 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(); + })); } }