|
|
@ -3,6 +3,7 @@ import { httpGet, httpPost } from './http'; |
|
|
|
import { TOKEN_COOKIE_NAME, getUser } from './jwt'; |
|
|
|
import { TOKEN_COOKIE_NAME, getUser } from './jwt'; |
|
|
|
// @ts-ignore
|
|
|
|
// @ts-ignore
|
|
|
|
import Element = astroHTML.JSX.Element; |
|
|
|
import Element = astroHTML.JSX.Element; |
|
|
|
|
|
|
|
import { roadmapProgress, totalRoadmapNodes } from '../stores/roadmap.ts'; |
|
|
|
|
|
|
|
|
|
|
|
export type ResourceType = 'roadmap' | 'best-practice'; |
|
|
|
export type ResourceType = 'roadmap' | 'best-practice'; |
|
|
|
export type ResourceProgressType = |
|
|
|
export type ResourceProgressType = |
|
|
@ -27,7 +28,7 @@ export async function isTopicDone(topic: TopicMeta): Promise<boolean> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function getTopicStatus( |
|
|
|
export async function getTopicStatus( |
|
|
|
topic: TopicMeta |
|
|
|
topic: TopicMeta, |
|
|
|
): Promise<ResourceProgressType> { |
|
|
|
): Promise<ResourceProgressType> { |
|
|
|
const { topicId, resourceType, resourceId } = topic; |
|
|
|
const { topicId, resourceType, resourceId } = topic; |
|
|
|
const progressResult = await getResourceProgress(resourceType, resourceId); |
|
|
|
const progressResult = await getResourceProgress(resourceType, resourceId); |
|
|
@ -49,7 +50,7 @@ export async function getTopicStatus( |
|
|
|
|
|
|
|
|
|
|
|
export async function updateResourceProgress( |
|
|
|
export async function updateResourceProgress( |
|
|
|
topic: TopicMeta, |
|
|
|
topic: TopicMeta, |
|
|
|
progressType: ResourceProgressType |
|
|
|
progressType: ResourceProgressType, |
|
|
|
) { |
|
|
|
) { |
|
|
|
const { topicId, resourceType, resourceId } = topic; |
|
|
|
const { topicId, resourceType, resourceId } = topic; |
|
|
|
|
|
|
|
|
|
|
@ -74,7 +75,7 @@ export async function updateResourceProgress( |
|
|
|
resourceId, |
|
|
|
resourceId, |
|
|
|
response.done, |
|
|
|
response.done, |
|
|
|
response.learning, |
|
|
|
response.learning, |
|
|
|
response.skipped |
|
|
|
response.skipped, |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
return response; |
|
|
@ -82,7 +83,7 @@ export async function updateResourceProgress( |
|
|
|
|
|
|
|
|
|
|
|
export async function getResourceProgress( |
|
|
|
export async function getResourceProgress( |
|
|
|
resourceType: 'roadmap' | 'best-practice', |
|
|
|
resourceType: 'roadmap' | 'best-practice', |
|
|
|
resourceId: string |
|
|
|
resourceId: string, |
|
|
|
): Promise<{ done: string[]; learning: string[]; skipped: string[] }> { |
|
|
|
): Promise<{ done: string[]; learning: string[]; skipped: string[] }> { |
|
|
|
// No need to load progress if user is not logged in
|
|
|
|
// No need to load progress if user is not logged in
|
|
|
|
if (!Cookies.get(TOKEN_COOKIE_NAME)) { |
|
|
|
if (!Cookies.get(TOKEN_COOKIE_NAME)) { |
|
|
@ -109,6 +110,14 @@ export async function getResourceProgress( |
|
|
|
|
|
|
|
|
|
|
|
if (!progress || isProgressExpired) { |
|
|
|
if (!progress || isProgressExpired) { |
|
|
|
return loadFreshProgress(resourceType, resourceId); |
|
|
|
return loadFreshProgress(resourceType, resourceId); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
setResourceProgress( |
|
|
|
|
|
|
|
resourceType, |
|
|
|
|
|
|
|
resourceId, |
|
|
|
|
|
|
|
progress?.done || [], |
|
|
|
|
|
|
|
progress?.learning || [], |
|
|
|
|
|
|
|
progress?.skipped || [], |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Dispatch event to update favorite status in the MarkFavorite component
|
|
|
|
// Dispatch event to update favorite status in the MarkFavorite component
|
|
|
@ -119,7 +128,7 @@ export async function getResourceProgress( |
|
|
|
resourceId, |
|
|
|
resourceId, |
|
|
|
isFavorite, |
|
|
|
isFavorite, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}), |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return progress; |
|
|
|
return progress; |
|
|
@ -127,7 +136,7 @@ export async function getResourceProgress( |
|
|
|
|
|
|
|
|
|
|
|
async function loadFreshProgress( |
|
|
|
async function loadFreshProgress( |
|
|
|
resourceType: ResourceType, |
|
|
|
resourceType: ResourceType, |
|
|
|
resourceId: string |
|
|
|
resourceId: string, |
|
|
|
) { |
|
|
|
) { |
|
|
|
const { response, error } = await httpGet<{ |
|
|
|
const { response, error } = await httpGet<{ |
|
|
|
done: string[]; |
|
|
|
done: string[]; |
|
|
@ -153,7 +162,7 @@ async function loadFreshProgress( |
|
|
|
resourceId, |
|
|
|
resourceId, |
|
|
|
response?.done || [], |
|
|
|
response?.done || [], |
|
|
|
response?.learning || [], |
|
|
|
response?.learning || [], |
|
|
|
response?.skipped || [] |
|
|
|
response?.skipped || [], |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Dispatch event to update favorite status in the MarkFavorite component
|
|
|
|
// Dispatch event to update favorite status in the MarkFavorite component
|
|
|
@ -164,7 +173,7 @@ async function loadFreshProgress( |
|
|
|
resourceId, |
|
|
|
resourceId, |
|
|
|
isFavorite: response.isFavorite, |
|
|
|
isFavorite: response.isFavorite, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}), |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
return response; |
|
|
@ -175,8 +184,14 @@ export function setResourceProgress( |
|
|
|
resourceId: string, |
|
|
|
resourceId: string, |
|
|
|
done: string[], |
|
|
|
done: string[], |
|
|
|
learning: string[], |
|
|
|
learning: string[], |
|
|
|
skipped: string[] |
|
|
|
skipped: string[], |
|
|
|
): void { |
|
|
|
): void { |
|
|
|
|
|
|
|
roadmapProgress.set({ |
|
|
|
|
|
|
|
done, |
|
|
|
|
|
|
|
learning, |
|
|
|
|
|
|
|
skipped, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const userId = getUser()?.id; |
|
|
|
const userId = getUser()?.id; |
|
|
|
localStorage.setItem( |
|
|
|
localStorage.setItem( |
|
|
|
`${resourceType}-${resourceId}-${userId}-progress`, |
|
|
|
`${resourceType}-${resourceId}-${userId}-progress`, |
|
|
@ -185,13 +200,13 @@ export function setResourceProgress( |
|
|
|
learning, |
|
|
|
learning, |
|
|
|
skipped, |
|
|
|
skipped, |
|
|
|
timestamp: new Date().getTime(), |
|
|
|
timestamp: new Date().getTime(), |
|
|
|
}) |
|
|
|
}), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function topicSelectorAll( |
|
|
|
export function topicSelectorAll( |
|
|
|
topicId: string, |
|
|
|
topicId: string, |
|
|
|
parentElement: Document | SVGElement | HTMLDivElement = document |
|
|
|
parentElement: Document | SVGElement | HTMLDivElement = document, |
|
|
|
): Element[] { |
|
|
|
): Element[] { |
|
|
|
const matchingElements: Element[] = []; |
|
|
|
const matchingElements: Element[] = []; |
|
|
|
|
|
|
|
|
|
|
@ -215,7 +230,7 @@ export function topicSelectorAll( |
|
|
|
`[data-node-id="${topicId}"]`, // Matching custom roadmap nodes
|
|
|
|
`[data-node-id="${topicId}"]`, // Matching custom roadmap nodes
|
|
|
|
`[data-id="${topicId}"]`, // Matching custom roadmap nodes
|
|
|
|
`[data-id="${topicId}"]`, // Matching custom roadmap nodes
|
|
|
|
], |
|
|
|
], |
|
|
|
parentElement |
|
|
|
parentElement, |
|
|
|
).forEach((element) => { |
|
|
|
).forEach((element) => { |
|
|
|
matchingElements.push(element); |
|
|
|
matchingElements.push(element); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -225,7 +240,7 @@ export function topicSelectorAll( |
|
|
|
|
|
|
|
|
|
|
|
export function renderTopicProgress( |
|
|
|
export function renderTopicProgress( |
|
|
|
topicId: string, |
|
|
|
topicId: string, |
|
|
|
topicProgress: ResourceProgressType |
|
|
|
topicProgress: ResourceProgressType, |
|
|
|
) { |
|
|
|
) { |
|
|
|
const isLearning = topicProgress === 'learning'; |
|
|
|
const isLearning = topicProgress === 'learning'; |
|
|
|
const isSkipped = topicProgress === 'skipped'; |
|
|
|
const isSkipped = topicProgress === 'skipped'; |
|
|
@ -268,7 +283,7 @@ export function clearResourceProgress() { |
|
|
|
|
|
|
|
|
|
|
|
export async function renderResourceProgress( |
|
|
|
export async function renderResourceProgress( |
|
|
|
resourceType: ResourceType, |
|
|
|
resourceType: ResourceType, |
|
|
|
resourceId: string |
|
|
|
resourceId: string, |
|
|
|
) { |
|
|
|
) { |
|
|
|
const { |
|
|
|
const { |
|
|
|
done = [], |
|
|
|
done = [], |
|
|
@ -293,7 +308,7 @@ export async function renderResourceProgress( |
|
|
|
|
|
|
|
|
|
|
|
function getMatchingElements( |
|
|
|
function getMatchingElements( |
|
|
|
quries: string[], |
|
|
|
quries: string[], |
|
|
|
parentElement: Document | SVGElement | HTMLDivElement = document |
|
|
|
parentElement: Document | SVGElement | HTMLDivElement = document, |
|
|
|
): Element[] { |
|
|
|
): Element[] { |
|
|
|
const matchingElements: Element[] = []; |
|
|
|
const matchingElements: Element[] = []; |
|
|
|
quries.forEach((query) => { |
|
|
|
quries.forEach((query) => { |
|
|
@ -306,7 +321,7 @@ function getMatchingElements( |
|
|
|
|
|
|
|
|
|
|
|
export function refreshProgressCounters() { |
|
|
|
export function refreshProgressCounters() { |
|
|
|
const progressNumsContainers = document.querySelectorAll( |
|
|
|
const progressNumsContainers = document.querySelectorAll( |
|
|
|
'[data-progress-nums-container]' |
|
|
|
'[data-progress-nums-container]', |
|
|
|
); |
|
|
|
); |
|
|
|
const progressNums = document.querySelectorAll('[data-progress-nums]'); |
|
|
|
const progressNums = document.querySelectorAll('[data-progress-nums]'); |
|
|
|
if (progressNumsContainers.length === 0 || progressNums.length === 0) { |
|
|
|
if (progressNumsContainers.length === 0 || progressNums.length === 0) { |
|
|
@ -322,27 +337,27 @@ export function refreshProgressCounters() { |
|
|
|
]).length; |
|
|
|
]).length; |
|
|
|
|
|
|
|
|
|
|
|
const externalLinks = document.querySelectorAll( |
|
|
|
const externalLinks = document.querySelectorAll( |
|
|
|
'[data-group-id^="ext_link:"]' |
|
|
|
'[data-group-id^="ext_link:"]', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
const roadmapSwitchers = document.querySelectorAll( |
|
|
|
const roadmapSwitchers = document.querySelectorAll( |
|
|
|
'[data-group-id^="json:"]' |
|
|
|
'[data-group-id^="json:"]', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
const checkBoxes = document.querySelectorAll( |
|
|
|
const checkBoxes = document.querySelectorAll( |
|
|
|
'[data-group-id^="check:"]' |
|
|
|
'[data-group-id^="check:"]', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
|
|
|
|
|
|
|
|
const totalCheckBoxesDone = document.querySelectorAll( |
|
|
|
const totalCheckBoxesDone = document.querySelectorAll( |
|
|
|
'[data-group-id^="check:"].done' |
|
|
|
'[data-group-id^="check:"].done', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
const totalCheckBoxesLearning = document.querySelectorAll( |
|
|
|
const totalCheckBoxesLearning = document.querySelectorAll( |
|
|
|
'[data-group-id^="check:"].learning' |
|
|
|
'[data-group-id^="check:"].learning', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
const totalCheckBoxesSkipped = document.querySelectorAll( |
|
|
|
const totalCheckBoxesSkipped = document.querySelectorAll( |
|
|
|
'[data-group-id^="check:"].skipped' |
|
|
|
'[data-group-id^="check:"].skipped', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
|
|
|
|
|
|
|
|
const totalRemoved = document.querySelectorAll( |
|
|
|
const totalRemoved = document.querySelectorAll( |
|
|
|
'.clickable-group.removed' |
|
|
|
'.clickable-group.removed', |
|
|
|
).length; |
|
|
|
).length; |
|
|
|
const totalItems = |
|
|
|
const totalItems = |
|
|
|
totalClickable - |
|
|
|
totalClickable - |
|
|
@ -351,6 +366,8 @@ export function refreshProgressCounters() { |
|
|
|
checkBoxes - |
|
|
|
checkBoxes - |
|
|
|
totalRemoved; |
|
|
|
totalRemoved; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
totalRoadmapNodes.set(totalItems); |
|
|
|
|
|
|
|
|
|
|
|
const totalDone = |
|
|
|
const totalDone = |
|
|
|
getMatchingElements([ |
|
|
|
getMatchingElements([ |
|
|
|
'.clickable-group.done:not([data-group-id^="ext_link:"])', |
|
|
|
'.clickable-group.done:not([data-group-id^="ext_link:"])', |
|
|
@ -373,47 +390,47 @@ export function refreshProgressCounters() { |
|
|
|
const doneCountEls = document.querySelectorAll('[data-progress-done]'); |
|
|
|
const doneCountEls = document.querySelectorAll('[data-progress-done]'); |
|
|
|
if (doneCountEls.length > 0) { |
|
|
|
if (doneCountEls.length > 0) { |
|
|
|
doneCountEls.forEach( |
|
|
|
doneCountEls.forEach( |
|
|
|
(doneCountEl) => (doneCountEl.innerHTML = `${totalDone}`) |
|
|
|
(doneCountEl) => (doneCountEl.innerHTML = `${totalDone}`), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const learningCountEls = document.querySelectorAll( |
|
|
|
const learningCountEls = document.querySelectorAll( |
|
|
|
'[data-progress-learning]' |
|
|
|
'[data-progress-learning]', |
|
|
|
); |
|
|
|
); |
|
|
|
if (learningCountEls.length > 0) { |
|
|
|
if (learningCountEls.length > 0) { |
|
|
|
learningCountEls.forEach( |
|
|
|
learningCountEls.forEach( |
|
|
|
(learningCountEl) => (learningCountEl.innerHTML = `${totalLearning}`) |
|
|
|
(learningCountEl) => (learningCountEl.innerHTML = `${totalLearning}`), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const skippedCountEls = document.querySelectorAll('[data-progress-skipped]'); |
|
|
|
const skippedCountEls = document.querySelectorAll('[data-progress-skipped]'); |
|
|
|
if (skippedCountEls.length > 0) { |
|
|
|
if (skippedCountEls.length > 0) { |
|
|
|
skippedCountEls.forEach( |
|
|
|
skippedCountEls.forEach( |
|
|
|
(skippedCountEl) => (skippedCountEl.innerHTML = `${totalSkipped}`) |
|
|
|
(skippedCountEl) => (skippedCountEl.innerHTML = `${totalSkipped}`), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const totalCountEls = document.querySelectorAll('[data-progress-total]'); |
|
|
|
const totalCountEls = document.querySelectorAll('[data-progress-total]'); |
|
|
|
if (totalCountEls.length > 0) { |
|
|
|
if (totalCountEls.length > 0) { |
|
|
|
totalCountEls.forEach( |
|
|
|
totalCountEls.forEach( |
|
|
|
(totalCountEl) => (totalCountEl.innerHTML = `${totalItems}`) |
|
|
|
(totalCountEl) => (totalCountEl.innerHTML = `${totalItems}`), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const progressPercentage = |
|
|
|
const progressPercentage = |
|
|
|
Math.round(((totalDone + totalSkipped) / totalItems) * 100) || 0; |
|
|
|
Math.round(((totalDone + totalSkipped) / totalItems) * 100) || 0; |
|
|
|
const progressPercentageEls = document.querySelectorAll( |
|
|
|
const progressPercentageEls = document.querySelectorAll( |
|
|
|
'[data-progress-percentage]' |
|
|
|
'[data-progress-percentage]', |
|
|
|
); |
|
|
|
); |
|
|
|
if (progressPercentageEls.length > 0) { |
|
|
|
if (progressPercentageEls.length > 0) { |
|
|
|
progressPercentageEls.forEach( |
|
|
|
progressPercentageEls.forEach( |
|
|
|
(progressPercentageEl) => |
|
|
|
(progressPercentageEl) => |
|
|
|
(progressPercentageEl.innerHTML = `${progressPercentage}`) |
|
|
|
(progressPercentageEl.innerHTML = `${progressPercentage}`), |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
progressNumsContainers.forEach((progressNumsContainer) => |
|
|
|
progressNumsContainers.forEach((progressNumsContainer) => |
|
|
|
progressNumsContainer.classList.remove('striped-loader') |
|
|
|
progressNumsContainer.classList.remove('striped-loader'), |
|
|
|
); |
|
|
|
); |
|
|
|
progressNums.forEach((progressNum) => { |
|
|
|
progressNums.forEach((progressNum) => { |
|
|
|
progressNum.classList.remove('opacity-0'); |
|
|
|
progressNum.classList.remove('opacity-0'); |
|
|
|