From 32673c21fb05fd0f59d30db9d3039e9e6eeb5479 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed
Date: Sat, 17 Jun 2023 15:10:10 +0100
Subject: [PATCH] Add shortcuts for progress tracking
---
src/components/FrameRenderer/renderer.ts | 81 ++++++++++++++++++-
src/components/ProgressHelpPopup.astro | 47 +++++++++++
src/components/ResourceProgressStats.astro | 33 +++++---
src/components/RoadmapHeader.astro | 2 +
src/components/TopicDetail/TopicDetail.tsx | 16 +---
.../TopicDetail/TopicProgressButton.tsx | 6 +-
src/components/YouTubeAlert.astro | 2 +-
src/icons/question.svg | 1 +
src/lib/popup.ts | 14 ++++
src/styles/global.css | 4 +
10 files changed, 174 insertions(+), 32 deletions(-)
create mode 100644 src/components/ProgressHelpPopup.astro
create mode 100644 src/icons/question.svg
create mode 100644 src/lib/popup.ts
diff --git a/src/components/FrameRenderer/renderer.ts b/src/components/FrameRenderer/renderer.ts
index 9cf5effb9..63ad29a77 100644
--- a/src/components/FrameRenderer/renderer.ts
+++ b/src/components/FrameRenderer/renderer.ts
@@ -2,13 +2,19 @@ import { wireframeJSONToSVG } from 'roadmap-renderer';
import { httpPost } from '../../lib/http';
import { isLoggedIn } from '../../lib/jwt';
import {
+ refreshProgressCounters,
renderResourceProgress,
+ renderTopicProgress,
+ ResourceProgressType,
ResourceType,
+ updateResourceProgress,
} from '../../lib/resource-progress';
+import { pageProgressMessage } from '../../stores/page';
+import { showLoginPopup } from '../../lib/popup';
export class Renderer {
resourceId: string;
- resourceType: string;
+ resourceType: ResourceType | string;
jsonUrl: string;
loaderHTML: string | null;
@@ -28,8 +34,10 @@ export class Renderer {
this.onDOMLoaded = this.onDOMLoaded.bind(this);
this.jsonToSvg = this.jsonToSvg.bind(this);
this.handleSvgClick = this.handleSvgClick.bind(this);
+ this.handleSvgRightClick = this.handleSvgRightClick.bind(this);
this.prepareConfig = this.prepareConfig.bind(this);
this.switchRoadmap = this.switchRoadmap.bind(this);
+ this.updateTopicStatus = this.updateTopicStatus.bind(this);
}
get loaderEl() {
@@ -161,6 +169,53 @@ export class Renderer {
this.jsonToSvg(newJsonUrl)?.then(() => {});
}
+ updateTopicStatus(topicId: string, newStatus: ResourceProgressType) {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ pageProgressMessage.set('Updating progress');
+ updateResourceProgress(
+ {
+ resourceId: this.resourceId,
+ resourceType: this.resourceType as ResourceType,
+ topicId,
+ },
+ newStatus
+ )
+ .then(() => {
+ renderTopicProgress(topicId, newStatus);
+ refreshProgressCounters();
+ })
+ .catch((err) => {
+ alert('Something went wrong, please try again.');
+ console.error(err);
+ })
+ .finally(() => {
+ pageProgressMessage.set('');
+ });
+
+ return;
+ }
+
+ handleSvgRightClick(e: any) {
+ const targetGroup = e.target?.closest('g') || {};
+ const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
+ if (!groupId) {
+ return;
+ }
+
+ e.preventDefault();
+
+ const isCurrentStatusDone = targetGroup.classList.contains('done');
+ const normalizedGroupId = groupId.replace(/^\d+-/, '');
+ this.updateTopicStatus(
+ normalizedGroupId,
+ !isCurrentStatusDone ? 'done' : 'pending'
+ );
+ }
+
handleSvgClick(e: any) {
const targetGroup = e.target?.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
@@ -209,6 +264,28 @@ export class Renderer {
// Remove sorting prefix from groupId
const normalizedGroupId = groupId.replace(/^\d+-/, '');
+ const isCurrentStatusLearning = targetGroup.classList.contains('learning');
+ const isCurrentStatusSkipped = targetGroup.classList.contains('skipped');
+
+ if (e.shiftKey) {
+ e.preventDefault();
+ this.updateTopicStatus(
+ normalizedGroupId,
+ !isCurrentStatusLearning ? 'learning' : 'pending'
+ );
+ return;
+ }
+
+ if (e.altKey) {
+ e.preventDefault();
+ this.updateTopicStatus(
+ normalizedGroupId,
+ !isCurrentStatusSkipped ? 'skipped' : 'pending'
+ );
+
+ return;
+ }
+
window.dispatchEvent(
new CustomEvent(`${this.resourceType}.topic.click`, {
detail: {
@@ -223,7 +300,7 @@ export class Renderer {
init() {
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
window.addEventListener('click', this.handleSvgClick);
- // window.addEventListener('contextmenu', this.handleSvgClick);
+ window.addEventListener('contextmenu', this.handleSvgRightClick);
}
}
diff --git a/src/components/ProgressHelpPopup.astro b/src/components/ProgressHelpPopup.astro
new file mode 100644
index 000000000..3aa6d92ec
--- /dev/null
+++ b/src/components/ProgressHelpPopup.astro
@@ -0,0 +1,47 @@
+---
+import AstroIcon from './AstroIcon.astro';
+import Popup from './Popup/Popup.astro';
+---
+
+
+
+
+ Track your Progress
+
+
+ Login and use one of the options listed below.
+
+
+
+
+
Option 1
+
+ Click the roadmap topics and use Update Progress dropdown to update your progress.
+
+
+
+
+
Option 2
+
Use the keyboard shortcuts listed below.
+
+
+ -
+ Right Mouse Click to mark as Done.
+
+ -
+ Shift + Click to mark as in progress.
+
+ -
+ Option + Click to mark as skipped.
+
+
+
+
+
+
diff --git a/src/components/ResourceProgressStats.astro b/src/components/ResourceProgressStats.astro
index de0bc1813..93e5fcf34 100644
--- a/src/components/ResourceProgressStats.astro
+++ b/src/components/ResourceProgressStats.astro
@@ -1,4 +1,5 @@
---
+import AstroIcon from './AstroIcon.astro';
export interface Props {
isSecondaryBanner?: boolean;
}
@@ -37,21 +38,31 @@ const { isSecondaryBanner = false } = Astro.props;
>
0 Total
+
+
-
-
- 0% Done
-
-
-
- 0 of 0 Done
-
+
+ 0 of 0 Done
+
+
diff --git a/src/components/RoadmapHeader.astro b/src/components/RoadmapHeader.astro
index c1c7ba6d1..5cbb0595f 100644
--- a/src/components/RoadmapHeader.astro
+++ b/src/components/RoadmapHeader.astro
@@ -5,6 +5,7 @@ import RoadmapHint from './RoadmapHint.astro';
import RoadmapNote from './RoadmapNote.astro';
import TopicSearch from './TopicSearch/TopicSearch.astro';
import YouTubeAlert from './YouTubeAlert.astro';
+import ProgressHelpPopup from './ProgressHelpPopup.astro';
export interface Props {
title: string;
@@ -32,6 +33,7 @@ const isRoadmapReady = !isUpcoming;
---
+
diff --git a/src/components/TopicDetail/TopicDetail.tsx b/src/components/TopicDetail/TopicDetail.tsx
index 10afd0574..799385077 100644
--- a/src/components/TopicDetail/TopicDetail.tsx
+++ b/src/components/TopicDetail/TopicDetail.tsx
@@ -18,6 +18,7 @@ import {
import { pageProgressMessage, sponsorHidden } from '../../stores/page';
import { TopicProgressButton } from './TopicProgressButton';
import { ContributionForm } from './ContributionForm';
+import { showLoginPopup } from '../../lib/popup';
export function TopicDetail() {
const [contributionAlertMessage, setContributionAlertMessage] = useState('');
@@ -35,20 +36,6 @@ export function TopicDetail() {
const [resourceId, setResourceId] = useState('');
const [resourceType, setResourceType] = useState
('roadmap');
- const showLoginPopup = () => {
- const popupEl = document.querySelector(`#login-popup`);
- if (!popupEl) {
- return;
- }
-
- popupEl.classList.remove('hidden');
- popupEl.classList.add('flex');
- const focusEl = popupEl.querySelector('[autofocus]');
- if (focusEl) {
- focusEl.focus();
- }
- };
-
// Close the topic detail when user clicks outside the topic detail
useOutsideClick(topicRef, () => {
setIsActive(false);
@@ -188,7 +175,6 @@ export function TopicDetail() {
topicId={topicId}
resourceId={resourceId}
resourceType={resourceType}
- onShowLoginPopup={showLoginPopup}
onClose={() => {
setIsActive(false);
setIsContributing(false);
diff --git a/src/components/TopicDetail/TopicProgressButton.tsx b/src/components/TopicDetail/TopicProgressButton.tsx
index 80844d736..24f64d661 100644
--- a/src/components/TopicDetail/TopicProgressButton.tsx
+++ b/src/components/TopicDetail/TopicProgressButton.tsx
@@ -12,13 +12,13 @@ import {
renderTopicProgress,
updateResourceProgress,
} from '../../lib/resource-progress';
+import { showLoginPopup } from '../../lib/popup';
type TopicProgressButtonProps = {
topicId: string;
resourceId: string;
resourceType: ResourceType;
- onShowLoginPopup: () => void;
onClose: () => void;
};
@@ -30,7 +30,7 @@ const statusColors: Record = {
};
export function TopicProgressButton(props: TopicProgressButtonProps) {
- const { topicId, resourceId, resourceType, onClose, onShowLoginPopup } =
+ const { topicId, resourceId, resourceType, onClose } =
props;
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true);
@@ -119,7 +119,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
if (isGuest) {
onClose();
- onShowLoginPopup();
+ showLoginPopup();
return;
}
diff --git a/src/components/YouTubeAlert.astro b/src/components/YouTubeAlert.astro
index d655311fd..cd0ec7a42 100644
--- a/src/components/YouTubeAlert.astro
+++ b/src/components/YouTubeAlert.astro
@@ -7,6 +7,6 @@
class="bg-red-600 group-hover:bg-red-800 group-hover: px-1.5 py-0.5 rounded-sm text-white text-xs uppercase font-medium mr-2"
>New
- We also have a YouTube channel with visual content.
+ We also have a YouTube channel with visual content
»
diff --git a/src/icons/question.svg b/src/icons/question.svg
new file mode 100644
index 000000000..72f6337d8
--- /dev/null
+++ b/src/icons/question.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/lib/popup.ts b/src/lib/popup.ts
new file mode 100644
index 000000000..e2d96514f
--- /dev/null
+++ b/src/lib/popup.ts
@@ -0,0 +1,14 @@
+export function showLoginPopup() {
+ const popupEl = document.querySelector(`#login-popup`);
+ if (!popupEl) {
+ return;
+ }
+
+ popupEl.classList.remove('hidden');
+ popupEl.classList.add('flex');
+
+ const focusEl = popupEl.querySelector('[autofocus]');
+ if (focusEl) {
+ focusEl.focus();
+ }
+}
diff --git a/src/styles/global.css b/src/styles/global.css
index 12bce1559..9f5819ca9 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -18,6 +18,10 @@
}
}
+svg {
+ user-select: none;
+}
+
blockquote p:before {
display: none;
}