Refactor buttons

pull/3915/head
Kamran Ahmed 2 years ago
parent 426fe44dc8
commit f4635d794f
  1. 81
      src/components/TopicDetail/TopicDetail.tsx
  2. 82
      src/components/TopicOverlay/TopicOverlay.astro
  3. 331
      src/components/TopicOverlay/topic.js

@ -187,9 +187,14 @@ export function TopicDetail() {
return null; return null;
} }
const contributionDir = resourceType === 'roadmap' ? 'roadmaps' : 'best-practices'; const contributionDir =
resourceType === 'roadmap' ? 'roadmaps' : 'best-practices';
const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`; const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`;
const allowMarkingDone = ['pending', 'learning'].includes(progress);
const allowMarkingLearning = ['pending'].includes(progress);
const allowMarkingPending = ['done', 'learning'].includes(progress);
return ( return (
<div> <div>
<div <div
@ -218,7 +223,7 @@ export function TopicDetail() {
onClick={() => setIsActive(false)} onClick={() => setIsActive(false)}
> >
<img alt="Check" class="w-3" src={CheckIcon} /> <img alt="Check" class="w-3" src={CheckIcon} />
<span className="ml-2">Done</span> <span className="ml-2">Mark as Done</span>
</button> </button>
<button <button
data-popup="login-popup" data-popup="login-popup"
@ -232,7 +237,7 @@ export function TopicDetail() {
)} )}
{!isGuest && ( {!isGuest && (
<> <div class="flex items-center gap-2 rounded-md">
{isUpdatingProgress && ( {isUpdatingProgress && (
<button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black"> <button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black">
<img <img
@ -243,55 +248,37 @@ export function TopicDetail() {
<span className="ml-2">Updating Status..</span> <span className="ml-2">Updating Status..</span>
</button> </button>
)} )}
{!isUpdatingProgress && progress === 'pending' && (
<div className="flex items-center gap-2"> {!isUpdatingProgress && allowMarkingDone && (
<button <button
className="inline-flex items-center rounded-md border border-green-600 bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700" className="inline-flex items-center rounded-md border border-green-600 bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
onClick={() => handleUpdateResourceProgress('done')} onClick={() => handleUpdateResourceProgress('done')}
> >
<img alt="Check" class="w-3" src={CheckIcon} /> <img alt="Check" class="w-3" src={CheckIcon} />
<span className="ml-2">Done</span> <span className="ml-2">Mark as Done</span>
</button> </button>
)}
<button
className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]" {!isUpdatingProgress && allowMarkingLearning && (
onClick={() => handleUpdateResourceProgress('learning')} <button
> className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]"
<img alt="Learning" class="w-4" src={ProgressIcon} /> onClick={() => handleUpdateResourceProgress('learning')}
<span className="ml-2">In Progress</span> >
</button> <img alt="Learning" className="w-4" src={ProgressIcon} />
</div> <span className="ml-2">In Progress</span>
</button>
)} )}
{!isUpdatingProgress && progress === 'done' && ( {!isUpdatingProgress && allowMarkingPending && (
<button <button
className="inline-flex items-center rounded-md border border-red-600 bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700" className="inline-flex items-center rounded-md border border-red-600 bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700"
onClick={() => handleUpdateResourceProgress('pending')} onClick={() => handleUpdateResourceProgress('pending')}
> >
<img alt="Check" class="h-4" src={ResetIcon} /> <img alt="Check" class="h-4" src={ResetIcon} />
<span className="ml-2">Pending</span> <span className="ml-2">Mark as Pending</span>
</button> </button>
)} )}
</div>
{!isUpdatingProgress && progress === 'learning' && (
<div className="flex items-center gap-2">
<button
className="inline-flex items-center rounded-md border border-green-600 bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
onClick={() => handleUpdateResourceProgress('done')}
>
<img alt="Check" class="w-3" src={CheckIcon} />
<span className="ml-2">Done</span>
</button>
<button
className="inline-flex items-center rounded-md border border-red-600 bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700"
onClick={() => handleUpdateResourceProgress('pending')}
>
<img alt="Check" class="h-4" src={ResetIcon} />
<span className="ml-2">Pending</span>
</button>
</div>
)}
</>
)} )}
<button <button
@ -313,10 +300,14 @@ export function TopicDetail() {
<p <p
id="contrib-meta" id="contrib-meta"
class="mt-10 border-t pt-3 text-sm text-gray-400 leading-relaxed" class="mt-10 border-t pt-3 text-sm leading-relaxed text-gray-400"
> >
Contribute links to learning resources about this topic{' '} Contribute links to learning resources about this topic{' '}
<a target="_blank" class="text-blue-700 underline" href={contributionUrl}> <a
target="_blank"
class="text-blue-700 underline"
href={contributionUrl}
>
on GitHub repository. on GitHub repository.
</a> </a>
. .

@ -1,82 +0,0 @@
---
import Icon from '../AstroIcon.astro';
import Loader from '../Loader.astro';
export interface Props {
contentContributionLink: string;
}
const { contentContributionLink } = Astro.props;
---
<div id='topic-overlay' class='hidden'>
<div
class='fixed right-0 top-0 z-40 h-screen w-full overflow-y-auto bg-white p-4 sm:max-w-[600px] sm:p-6'
tabindex='-1'
id='topic-body'
>
<div id='topic-loader' class='hidden'>
<Loader />
</div>
<div id='topic-actions' class='mb-2 hidden'>
<div data-guest-required class='hidden'>
<button
data-popup='login-popup'
class='inline-flex items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700'
>
<Icon icon='check' />
<span class='ml-2'>Done</span>
</button>
</div>
<div data-auth-required>
<button
id='mark-topic-done'
class='inline-flex hidden items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700'
>
<Icon icon='check' />
<span class='ml-2'>Done</span>
</button>
<button
id='mark-topic-pending'
class='inline-flex hidden items-center rounded-md bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700'
>
<Icon icon='reset' />
<span class='ml-2'>Pending</span>
</button>
</div>
<button
type='button'
id='close-topic'
class='absolute right-2.5 top-2.5 inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900'
>
<Icon icon='close' />
</button>
</div>
<div
id='topic-content'
class='prose prose-quoteless prose-h1:mb-2.5 prose-h1:mt-7 prose-h2:mb-3 prose-h2:mt-0 prose-h3:mb-[5px] prose-h3:mt-[10px] prose-p:mb-2 prose-p:mt-0 prose-blockquote:font-normal prose-blockquote:not-italic prose-blockquote:text-gray-700 prose-li:m-0 prose-li:mb-0.5'
>
</div>
<p
id='contrib-meta'
class='mt-10 hidden border-t pt-3 text-sm text-gray-400'
>
We are still working on this page. You can contribute by submitting a
brief description and a few links to learn more about this topic <a
target='_blank'
class='text-blue-700 underline'
href={contentContributionLink}>on GitHub repository.</a
>.
</p>
</div>
<div class='fixed inset-0 z-30 bg-gray-900 bg-opacity-50 dark:bg-opacity-80'>
</div>
</div>
<script src='./topic.js'></script>

@ -1,331 +0,0 @@
export class Topic {
constructor() {
this.overlayId = 'topic-overlay';
this.contentId = 'topic-content';
this.loaderId = 'topic-loader';
this.topicBodyId = 'topic-body';
this.topicActionsId = 'topic-actions';
this.markTopicDoneId = 'mark-topic-done';
this.markTopicPendingId = 'mark-topic-pending';
this.closeTopicId = 'close-topic';
this.contributionTextId = 'contrib-meta';
this.activeResourceType = null;
this.activeResourceId = null;
this.activeTopicId = null;
this.handleRoadmapTopicClick = this.handleRoadmapTopicClick.bind(this);
this.handleBestPracticeTopicClick =
this.handleBestPracticeTopicClick.bind(this);
this.handleBestPracticeTopicToggle =
this.handleBestPracticeTopicToggle.bind(this);
this.handleBestPracticeTopicPending =
this.handleBestPracticeTopicPending.bind(this);
this.close = this.close.bind(this);
this.resetDOM = this.resetDOM.bind(this);
this.populate = this.populate.bind(this);
this.handleOverlayClick = this.handleOverlayClick.bind(this);
this.markAsDone = this.markAsDone.bind(this);
this.markAsPending = this.markAsPending.bind(this);
this.querySvgElementsByTopicId = this.querySvgElementsByTopicId.bind(this);
this.isTopicDone = this.isTopicDone.bind(this);
this.init = this.init.bind(this);
}
get loaderEl() {
return document.getElementById(this.loaderId);
}
get markTopicDoneEl() {
return document.getElementById(this.markTopicDoneId);
}
get markTopicPendingEl() {
return document.getElementById(this.markTopicPendingId);
}
get topicActionsEl() {
return document.getElementById(this.topicActionsId);
}
get contributionTextEl() {
return document.getElementById(this.contributionTextId);
}
get contentEl() {
return document.getElementById(this.contentId);
}
get overlayEl() {
return document.getElementById(this.overlayId);
}
resetDOM(hideOverlay = false) {
if (hideOverlay) {
this.overlayEl.classList.add('hidden');
} else {
this.overlayEl.classList.remove('hidden');
}
this.loaderEl.classList.remove('hidden'); // Show loader
this.topicActionsEl.classList.add('hidden'); // Hide Actions
this.contributionTextEl.classList.add('hidden'); // Hide contribution text
this.contentEl.replaceChildren(''); // Remove content
}
close() {
this.resetDOM(true);
this.activeResourceId = null;
this.activeTopicId = null;
}
isTopicDone(topicId) {
const normalizedGroup = topicId.replace(/^\d+-/, '');
const el = document.querySelector(`[data-group-id$="-${normalizedGroup}"]`);
return el?.classList.contains('done');
}
/**
* @param {string | HTMLElement} html
*/
populate(html) {
this.contentEl.replaceChildren(html);
this.loaderEl.classList.add('hidden');
this.topicActionsEl.classList.remove('hidden');
this.contributionTextEl.classList.remove('hidden');
const isDone = this.isTopicDone(this.activeTopicId);
if (isDone) {
this.markTopicDoneEl.classList.add('hidden');
this.markTopicPendingEl.classList.remove('hidden');
} else {
this.markTopicDoneEl.classList.remove('hidden');
this.markTopicPendingEl.classList.add('hidden');
}
}
renderTopicFromUrl(url) {
return fetch(url)
.then((res) => {
return res.text();
})
.then((topicHtml) => {
// It's full HTML with page body, head etc.
// We only need the inner HTML of the #main-content
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
return node.getElementById('main-content');
})
.then((content) => {
this.populate(content);
})
.catch((e) => {
console.error(e);
this.populate('Error loading the content!');
});
}
handleBestPracticeTopicToggle(e) {
const { resourceId: bestPracticeId, topicId } = e.detail;
if (!topicId || !bestPracticeId) {
console.log('Missing topic or bestPracticeId: ', e.detail);
return;
}
const isDone = localStorage.getItem(topicId) === 'done';
if (isDone) {
this.markAsPending(topicId, bestPracticeId, 'best-practice');
} else {
this.markAsDone(topicId, bestPracticeId, 'best-practice');
}
}
handleBestPracticeTopicPending(e) {
const { resourceId: bestPracticeId, topicId } = e.detail;
if (!topicId || !bestPracticeId) {
console.log('Missing topic or bestPracticeId: ', e.detail);
return;
}
this.markAsPending(topicId, bestPracticeId, 'best-practice');
}
handleBestPracticeTopicClick(e) {
const { resourceId: bestPracticeId, topicId } = e.detail;
if (!topicId || !bestPracticeId) {
console.log('Missing topic or bestPracticeId: ', e.detail);
return;
}
this.activeResourceType = 'best-practice';
this.activeResourceId = bestPracticeId;
this.activeTopicId = topicId;
this.resetDOM();
const topicUrl = `/best-practices/${bestPracticeId}/${topicId.replaceAll(
':',
'/'
)}`;
this.renderTopicFromUrl(topicUrl).then(() => null);
}
handleRoadmapTopicClick(e) {
const { resourceId: roadmapId, topicId } = e.detail;
if (!topicId || !roadmapId) {
console.log('Missing topic or roadmap: ', e.detail);
return;
}
this.activeResourceType = 'roadmap';
this.activeResourceId = roadmapId;
this.activeTopicId = topicId;
this.resetDOM();
const topicUrl = `/${roadmapId}/${topicId.replaceAll(':', '/')}`;
window.fireEvent({
category: `RoadmapClick`,
action: `${roadmapId}/load-topic`,
label: topicUrl,
});
this.renderTopicFromUrl(topicUrl).then(() => null);
}
querySvgElementsByTopicId(topicId) {
const matchingElements = [];
// Elements having sort order in the beginning of the group id
document
.querySelectorAll(`[data-group-id$="-${topicId}"]`)
.forEach((element) => {
const foundGroupId = element?.dataset?.groupId || '';
const validGroupRegex = new RegExp(`^\\d+-${topicId}$`);
if (validGroupRegex.test(foundGroupId)) {
matchingElements.push(element);
}
});
// Elements with exact match of the topic id
document
.querySelectorAll(`[data-group-id="${topicId}"]`)
.forEach((element) => {
matchingElements.push(element);
});
// Matching "check:XXXX" box of the topic
document
.querySelectorAll(`[data-group-id="check:${topicId}"]`)
.forEach((element) => {
matchingElements.push(element);
});
return matchingElements;
}
async markAsDone(topicId, resourceId, resourceType) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
const { response, error } = {};
if (response) {
this.close();
this.querySvgElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.add('done');
});
} else {
console.error(error);
}
}
async markAsPending(topicId, resourceId, resourceType) {
const updatedTopicId = topicId.replace(/^\d+-/, '');
const { response, error } = {};
if (response) {
this.close();
this.querySvgElementsByTopicId(updatedTopicId).forEach((item) => {
item?.classList?.remove('done');
});
} else {
console.error(error);
}
}
handleOverlayClick(e) {
const isClickedInsideTopic = e.target.closest(`#${this.topicBodyId}`);
if (!isClickedInsideTopic) {
this.close();
return;
}
const isClickedDone =
e.target.id === this.markTopicDoneId ||
e.target.closest(`#${this.markTopicDoneId}`);
if (isClickedDone) {
this.markAsDone(
this.activeTopicId,
this.activeResourceId,
this.activeResourceType
);
// this.close();
}
const isClickedPending =
e.target.id === this.markTopicPendingId ||
e.target.closest(`#${this.markTopicPendingId}`);
if (isClickedPending) {
this.markAsPending(
this.activeTopicId,
this.activeResourceId,
this.activeResourceType
);
// this.close();
}
const isClickedPopupOpener =
e.target.dataset['popup'] || e.target.closest('button[data-popup]');
const isClickedClose =
e.target.id === this.closeTopicId ||
e.target.closest(`#${this.closeTopicId}`);
if (isClickedClose || isClickedPopupOpener) {
this.close();
}
}
init() {
window.addEventListener(
'best-practice.topic.click',
this.handleBestPracticeTopicClick
);
window.addEventListener(
'best-practice.topic.toggle',
this.handleBestPracticeTopicToggle
);
window.addEventListener(
'roadmap.topic.click',
this.handleRoadmapTopicClick
);
window.addEventListener('click', this.handleOverlayClick);
window.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'escape') {
this.close();
}
});
}
}
// Initialize the topic loader
const topic = new Topic();
topic.init();
Loading…
Cancel
Save