mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 10:11:55 +08:00
Compare commits
1 Commits
feat/progr
...
feat/linke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9e0f7926e |
@@ -28,8 +28,6 @@
|
||||
"@nanostores/preact": "^0.5.0",
|
||||
"astro": "^2.6.3",
|
||||
"astro-compress": "^1.1.47",
|
||||
"chart.js": "^4.3.0",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"jose": "^4.14.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"nanostores": "^0.9.1",
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -11,8 +11,6 @@ specifiers:
|
||||
'@types/js-cookie': ^3.0.3
|
||||
astro: ^2.6.3
|
||||
astro-compress: ^1.1.47
|
||||
chart.js: ^4.3.0
|
||||
chartjs-plugin-datalabels: ^2.2.0
|
||||
csv-parser: ^3.0.0
|
||||
gh-pages: ^5.0.0
|
||||
jose: ^4.14.4
|
||||
@@ -39,8 +37,6 @@ dependencies:
|
||||
'@nanostores/preact': 0.5.0_m2wbkjxz7237icvaxqi7ignbgm
|
||||
astro: 2.6.4
|
||||
astro-compress: 1.1.47
|
||||
chart.js: 4.3.0
|
||||
chartjs-plugin-datalabels: 2.2.0_chart.js@4.3.0
|
||||
jose: 4.14.4
|
||||
js-cookie: 3.0.5
|
||||
nanostores: 0.9.1
|
||||
@@ -700,10 +696,6 @@ packages:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
|
||||
/@kurkle/color/0.3.2:
|
||||
resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
|
||||
dev: false
|
||||
|
||||
/@ljharb/has-package-exports-patterns/0.0.2:
|
||||
resolution: {integrity: sha512-4/RWEeXDO6bocPONheFe6gX/oQdP/bEpv0oL4HqjPP5DCenBSt0mHgahppY49N0CpsaqffdwPq+TlX9CYOq2Dw==}
|
||||
dev: false
|
||||
@@ -1561,21 +1553,6 @@ packages:
|
||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||
dev: false
|
||||
|
||||
/chart.js/4.3.0:
|
||||
resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==}
|
||||
engines: {pnpm: '>=7'}
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.2
|
||||
dev: false
|
||||
|
||||
/chartjs-plugin-datalabels/2.2.0_chart.js@4.3.0:
|
||||
resolution: {integrity: sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==}
|
||||
peerDependencies:
|
||||
chart.js: '>=3.0.0'
|
||||
dependencies:
|
||||
chart.js: 4.3.0
|
||||
dev: false
|
||||
|
||||
/chokidar/3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { Chart as ChartJS, ChartTypeRegistry } from 'chart.js/auto';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { ActivityCounters } from './ActivityCounters';
|
||||
import { ResourceProgress } from './ResourceProgress';
|
||||
@@ -52,15 +50,8 @@ type ActivityResponse = {
|
||||
}[];
|
||||
};
|
||||
|
||||
type ChartLegendItem = {
|
||||
title: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export function ActivityPage() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [activity, setActivity] = useState<ActivityResponse>();
|
||||
const [chartLegend, setChartLegend] = useState<ChartLegendItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
async function loadActivity() {
|
||||
@@ -92,56 +83,6 @@ export function ActivityPage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
return {
|
||||
labels: [...learningRoadmaps, ...learningBestPractices].map(resource => resource.title),
|
||||
data: [...learningRoadmaps, ...learningBestPractices].map(resource => resource.done)
|
||||
}
|
||||
}, [activity])
|
||||
|
||||
useEffect(() => {
|
||||
let chart: ChartJS<"pie", number[], string> | null = null
|
||||
const ctx = canvasRef.current?.getContext('2d');
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
chart = new ChartJS(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: chartData.labels,
|
||||
datasets: [{
|
||||
data: chartData.data,
|
||||
hoverOffset: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const legendItems = chart?.legend?.legendItems || []
|
||||
const enrichedLegendItems = legendItems.map((item, index) => {
|
||||
return {
|
||||
title: item.text,
|
||||
color: item.fillStyle?.toString() || ''
|
||||
}
|
||||
})
|
||||
console.log(enrichedLegendItems)
|
||||
setChartLegend(enrichedLegendItems)
|
||||
|
||||
return () => {
|
||||
chart?.destroy();
|
||||
};
|
||||
}, [chartData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActivityCounters
|
||||
@@ -150,35 +91,6 @@ export function ActivityPage() {
|
||||
streak={activity?.streak || { count: 0 }}
|
||||
/>
|
||||
|
||||
<div className="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
|
||||
<div className="bg-white shadow-lg rounded-2xl p-8">
|
||||
<h2 className="font-medium">Knowledge Structure</h2>
|
||||
<div className="grid grid-cols-4 gap-5 mt-6">
|
||||
<div className="w-full aspect-square flex items-center justify-center h-full">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-3">
|
||||
<div className="flex flex-col gap-1.5 justify-center h-full">
|
||||
{chartLegend.map((data) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
style={{
|
||||
background: `${data.color}`
|
||||
}}
|
||||
className="w-3 h-3 rounded-full"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">{data.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
|
||||
{learningRoadmaps.length === 0 &&
|
||||
learningBestPractices.length === 0 && <EmptyActivity />}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import Icon from './AstroIcon.astro';
|
||||
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
|
||||
import BestPracticeHint from './BestPracticeHint.astro';
|
||||
import ProgressHelpPopup from './ProgressHelpPopup.astro';
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
@@ -16,7 +15,6 @@ const isBestPracticeReady = !isUpcoming;
|
||||
---
|
||||
|
||||
<LoginPopup />
|
||||
<ProgressHelpPopup />
|
||||
|
||||
<div class='border-b'>
|
||||
<div class='container relative py-5 sm:py-12'>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
type FavoriteIconProps = {
|
||||
isFavorite?: boolean;
|
||||
};
|
||||
|
||||
export function FavoriteIcon(props: FavoriteIconProps) {
|
||||
const { isFavorite } = props;
|
||||
|
||||
if (!isFavorite) {
|
||||
return (
|
||||
<svg
|
||||
width="8"
|
||||
height="10"
|
||||
viewBox="0 0 8 10"
|
||||
fill="none"
|
||||
className="h-3.5 w-3.5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5ZM5.93682 1.25C6.42732 1.25 6.83382 1.632 6.86382 2.122L7.24932 8.506C7.25216 8.55018 7.24229 8.59425 7.22089 8.63301C7.19949 8.67176 7.16745 8.70359 7.12854 8.72472C7.08964 8.74585 7.0455 8.75542 7.00134 8.75228C6.95718 8.74914 6.91484 8.73343 6.87932 8.707L4.27582 6.783C4.19591 6.72397 4.09917 6.69211 3.99982 6.69211C3.90047 6.69211 3.80373 6.72397 3.72382 6.783L1.11982 8.707C1.0843 8.73343 1.04196 8.74914 0.9978 8.75228C0.953639 8.75542 0.909502 8.74585 0.8706 8.72472C0.831697 8.70359 0.799653 8.67176 0.778252 8.63301C0.756851 8.59425 0.746986 8.55018 0.749822 8.506L1.13632 2.122C1.16632 1.632 1.57232 1.25 2.06282 1.25H5.93682Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="8"
|
||||
height="10"
|
||||
viewBox="0 0 8 10"
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
import AstroIcon from '../AstroIcon.astro';
|
||||
import { MarkFavorite } from './MarkFavorite';
|
||||
export interface FeaturedItemType {
|
||||
isUpcoming?: boolean;
|
||||
isNew?: boolean;
|
||||
@@ -22,22 +20,16 @@ const { isUpcoming = false, isNew = false, text, url } = Astro.props;
|
||||
]}
|
||||
href={url}
|
||||
>
|
||||
<span class='relative z-20 text-slate-400'>
|
||||
<span class='text-slate-400 relative z-20'>
|
||||
{text}
|
||||
</span>
|
||||
|
||||
<MarkFavorite
|
||||
resourceId={url.split('/').pop()!}
|
||||
resourceType={url.includes('best-practices') ? 'best-practice' : 'roadmap'}
|
||||
client:load
|
||||
/>
|
||||
|
||||
{
|
||||
isNew && (
|
||||
<span class='absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-purple-300'>
|
||||
<span class='mr-1.5 flex h-2 w-2'>
|
||||
<span class='absolute inline-flex h-2 w-2 animate-ping rounded-full bg-purple-400 opacity-75' />
|
||||
<span class='relative inline-flex h-2 w-2 rounded-full bg-purple-500' />
|
||||
<span class='absolute bottom-1.5 right-2 text-xs font-medium rounded-br rounded-tl text-purple-300 flex items-center'>
|
||||
<span class='flex h-2 w-2 mr-1.5'>
|
||||
<span class='animate-ping absolute inline-flex h-2 w-2 rounded-full bg-purple-400 opacity-75' />
|
||||
<span class='relative inline-flex rounded-full h-2 w-2 bg-purple-500' />
|
||||
</span>
|
||||
New
|
||||
</span>
|
||||
@@ -46,17 +38,14 @@ const { isUpcoming = false, isNew = false, text, url } = Astro.props;
|
||||
|
||||
{
|
||||
isUpcoming && (
|
||||
<span class='absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-slate-500'>
|
||||
<span class='mr-1.5 flex h-2 w-2'>
|
||||
<span class='absolute inline-flex h-2 w-2 animate-ping rounded-full bg-slate-500 opacity-75' />
|
||||
<span class='relative inline-flex h-2 w-2 rounded-full bg-slate-600' />
|
||||
<span class='absolute bottom-1.5 right-2 text-xs font-medium rounded-br rounded-tl text-slate-500 flex items-center'>
|
||||
<span class='flex h-2 w-2 mr-1.5'>
|
||||
<span class='animate-ping absolute inline-flex h-2 w-2 rounded-full bg-slate-500 opacity-75' />
|
||||
<span class='relative inline-flex rounded-full h-2 w-2 bg-slate-600' />
|
||||
</span>
|
||||
Upcoming
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<span
|
||||
data-progress
|
||||
class='absolute bottom-0 left-0 top-0 z-10 w-0 bg-[#172a3a] transition-[width] duration-300'
|
||||
></span>
|
||||
<span data-progress class="z-10 bg-[#172a3a] absolute top-0 left-0 bottom-0 duration-300 transition-[width] w-0"></span>
|
||||
</a>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { httpPatch } from '../../lib/http';
|
||||
import type { ResourceType } from '../../lib/resource-progress';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import { FavoriteIcon } from './FavoriteIcon';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
|
||||
type MarkFavoriteType = {
|
||||
resourceType: ResourceType;
|
||||
resourceId: string;
|
||||
favorite?: boolean;
|
||||
};
|
||||
|
||||
export function MarkFavorite({ resourceId, resourceType, favorite }: MarkFavoriteType) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isFavorite, setIsFavorite] = useState(favorite ?? false);
|
||||
|
||||
async function toggleFavoriteHandler(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!isLoggedIn()) {
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const { error } = await httpPatch<{ status: 'ok' }>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-mark-favorite`,
|
||||
{
|
||||
resourceType,
|
||||
resourceId,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
setIsLoading(false);
|
||||
return alert('Failed to update favorite status');
|
||||
}
|
||||
|
||||
// Dispatching an event instead of setting the state because
|
||||
// MarkFavorite component is used in the HeroSection as well
|
||||
// as featured items section. We will let the custom event
|
||||
// listener set the update `useEffect`
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('mark-favorite', {
|
||||
detail: {
|
||||
resourceId,
|
||||
resourceType,
|
||||
isFavorite: !isFavorite,
|
||||
},
|
||||
})
|
||||
);
|
||||
window.dispatchEvent(new CustomEvent('refresh-favorites', {}));
|
||||
|
||||
setIsFavorite(!isFavorite);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e: Event) => {
|
||||
const {
|
||||
resourceId: id,
|
||||
resourceType: type,
|
||||
isFavorite: fav,
|
||||
} = (e as CustomEvent).detail;
|
||||
if (id === resourceId && type === resourceType) {
|
||||
setIsFavorite(fav);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('mark-favorite', listener);
|
||||
return () => {
|
||||
window.removeEventListener('mark-favorite', listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleFavoriteHandler}
|
||||
tabIndex={-1}
|
||||
className={`${
|
||||
isFavorite ? '' : 'opacity-30 hover:opacity-100'
|
||||
} absolute right-1.5 top-1.5 z-30 focus:outline-0`}
|
||||
>
|
||||
{isLoading ? <Spinner /> : <FavoriteIcon isFavorite={isFavorite} />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -2,19 +2,13 @@ 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: ResourceType | string;
|
||||
resourceType: string;
|
||||
jsonUrl: string;
|
||||
loaderHTML: string | null;
|
||||
|
||||
@@ -34,10 +28,8 @@ 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() {
|
||||
@@ -169,53 +161,6 @@ 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 : '';
|
||||
@@ -264,28 +209,6 @@ 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: {
|
||||
@@ -300,7 +223,7 @@ export class Renderer {
|
||||
init() {
|
||||
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
|
||||
window.addEventListener('click', this.handleSvgClick);
|
||||
window.addEventListener('contextmenu', this.handleSvgRightClick);
|
||||
// window.addEventListener('contextmenu', this.handleSvgClick);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
import { CheckIcon } from './CheckIcon';
|
||||
|
||||
type EmptyProgressProps = {
|
||||
title?: string;
|
||||
|
||||
@@ -7,7 +7,6 @@ export type UserProgressResponse = {
|
||||
resourceId: string;
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
resourceTitle: string;
|
||||
isFavorite: boolean;
|
||||
done: number;
|
||||
learning: number;
|
||||
skipped: number;
|
||||
@@ -26,16 +25,6 @@ function renderProgress(progressList: UserProgressResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('mark-favorite', {
|
||||
detail: {
|
||||
resourceId: progress.resourceId,
|
||||
resourceType: progress.resourceType,
|
||||
isFavorite: progress.isFavorite,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const totalDone = progress.done + progress.skipped;
|
||||
const percentageDone = (totalDone / progress.total) * 100;
|
||||
|
||||
@@ -72,7 +61,6 @@ export function FavoriteRoadmaps() {
|
||||
|
||||
async function loadProgress() {
|
||||
setIsLoading(true);
|
||||
|
||||
const { response: progressList, error } =
|
||||
await httpGet<UserProgressResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
|
||||
@@ -96,11 +84,6 @@ export function FavoriteRoadmaps() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('refresh-favorites', loadProgress);
|
||||
return () => window.removeEventListener('refresh-favorites', loadProgress);
|
||||
}, []);
|
||||
|
||||
if (isPreparing) {
|
||||
return null;
|
||||
}
|
||||
@@ -114,9 +97,10 @@ export function FavoriteRoadmaps() {
|
||||
}`}
|
||||
>
|
||||
<div className="container min-h-full">
|
||||
{isLoading && <EmptyProgress title="Loading progress .." />}
|
||||
{!isLoading && progress.length == 0 && <EmptyProgress />}
|
||||
{progress.length > 0 && (
|
||||
<ProgressList progress={progress} isLoading={isLoading} />
|
||||
{!isLoading && progress.length > 0 && (
|
||||
<ProgressList progress={progress} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
import type { UserProgressResponse } from './FavoriteRoadmaps';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
import { CheckIcon } from './CheckIcon';
|
||||
|
||||
type ProgressListProps = {
|
||||
progress: UserProgressResponse;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export function ProgressList(props: ProgressListProps) {
|
||||
const { progress, isLoading = false } = props;
|
||||
const { progress } = props;
|
||||
|
||||
return (
|
||||
<div className="relative pb-12 pt-4 sm:pt-7">
|
||||
<div className="relative pt-4 sm:pt-7 pb-12">
|
||||
<p className="mb-4 flex items-center text-sm text-gray-400">
|
||||
{!isLoading && (
|
||||
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
|
||||
)}
|
||||
{isLoading && (
|
||||
<span className="mr-1.5">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
Your progress and favorite roadmaps.
|
||||
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
|
||||
<span className='hidden sm:inline'>Your progress and favorite roadmaps.</span>
|
||||
<span className='inline sm:hidden'>Your progress and favorite roadmaps.</span>
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
|
||||
{progress.map((resource) => {
|
||||
const url =
|
||||
resource.resourceType === 'roadmap'
|
||||
@@ -37,21 +28,15 @@ export function ProgressList(props: ProgressListProps) {
|
||||
|
||||
return (
|
||||
<a
|
||||
key={resource.resourceId}
|
||||
href={url}
|
||||
className="relative flex flex-col overflow-hidden rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300"
|
||||
className="relative flex flex-col rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300 overflow-hidden"
|
||||
>
|
||||
<span className="relative z-20">{resource.resourceTitle}</span>
|
||||
<span className='relative z-20'>{resource.resourceTitle}</span>
|
||||
|
||||
<span
|
||||
class="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
|
||||
style={{ width: `${percentageDone}%` }}
|
||||
></span>
|
||||
<MarkFavorite
|
||||
resourceId={resource.resourceId}
|
||||
resourceType={resource.resourceType}
|
||||
favorite={resource.isFavorite}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -19,7 +19,6 @@ export class Popup {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
popupEl.classList.remove('hidden');
|
||||
popupEl.classList.add('flex');
|
||||
const focusEl = popupEl.querySelector('[autofocus]');
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
import AstroIcon from './AstroIcon.astro';
|
||||
import Popup from './Popup/Popup.astro';
|
||||
---
|
||||
|
||||
<Popup id='progress-help' title='' subtitle=''>
|
||||
<div class='-mt-2.5'>
|
||||
<h2 class='mb-3 text-2xl font-semibold leading-5 text-gray-900'>
|
||||
Track your Progress
|
||||
</h2>
|
||||
<p class='text-sm leading-4 text-gray-600'>
|
||||
Login and use one of the options listed below.
|
||||
</p>
|
||||
|
||||
<div class='mt-4 flex flex-col gap-1.5'>
|
||||
<div class='rounded-md border px-3 py-3 text-gray-500'>
|
||||
<span class='mb-1.5 block text-xs font-medium uppercase text-green-600'
|
||||
>Option 1</span
|
||||
>
|
||||
<p class='text-sm'>
|
||||
Click the roadmap topics and use <span class='underline'
|
||||
>Update Progress</span
|
||||
> dropdown to update your progress.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class='rounded-md border border-yellow-300 bg-yellow-50 px-3 py-3 text-gray-500'>
|
||||
<span class='mb-1.5 block text-xs font-medium uppercase text-green-600'
|
||||
>Option 2</span
|
||||
>
|
||||
<p class='text-sm'>Use the keyboard shortcuts listed below.</p>
|
||||
|
||||
<ul class="flex flex-col gap-1 mt-3 mb-1.5">
|
||||
<li class='text-sm leading-loose'>
|
||||
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Right Mouse Click</kbd> to mark as Done.
|
||||
</li>
|
||||
<li class='text-sm leading-loose'>
|
||||
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Shift</kbd> + <kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Click</kbd> to mark as in progress.
|
||||
</li>
|
||||
<li class='text-sm leading-loose'>
|
||||
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Option / Alt</kbd> + <kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Click</kbd> to mark as skipped.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
@@ -1,21 +0,0 @@
|
||||
export function Spinner() {
|
||||
return (
|
||||
<svg
|
||||
className="h-3.5 w-3.5 animate-spin"
|
||||
viewBox="0 0 93 93"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M46.5 93C72.1812 93 93 72.1812 93 46.5C93 20.8188 72.1812 0 46.5 0C20.8188 0 0 20.8188 0 46.5C0 72.1812 20.8188 93 46.5 93ZM46.5 77C63.3447 77 77 63.3447 77 46.5C77 29.6553 63.3447 16 46.5 16C29.6553 16 16 29.6553 16 46.5C16 63.3447 29.6553 77 46.5 77Z"
|
||||
style="fill: #404040;"
|
||||
></path>
|
||||
<path
|
||||
d="M84.9746 49.5667C89.3257 49.9135 93.2042 46.6479 92.81 42.3008C92.3588 37.3251 91.1071 32.437 89.0872 27.8298C86.0053 20.7998 81.2311 14.6422 75.1905 9.90623C69.15 5.17027 62.031 2.00329 54.4687 0.687889C49.5126 -0.174203 44.467 -0.223422 39.5274 0.525737C35.2118 1.18024 32.966 5.72596 34.3411 9.86865V9.86865C35.7161 14.0113 40.2118 16.1424 44.5681 15.8677C46.9635 15.7166 49.3773 15.8465 51.7599 16.2609C56.7515 17.1291 61.4505 19.2196 65.4377 22.3456C69.4249 25.4717 72.5762 29.5362 74.6105 34.1764C75.5815 36.3912 76.2835 38.7044 76.7084 41.0666C77.4811 45.3626 80.6234 49.2199 84.9746 49.5667V49.5667Z"
|
||||
style="fill: #94a3b8;"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
import AstroIcon from './AstroIcon.astro';
|
||||
export interface Props {
|
||||
isSecondaryBanner?: boolean;
|
||||
}
|
||||
@@ -38,31 +37,21 @@ const { isSecondaryBanner = false } = Astro.props;
|
||||
>
|
||||
<span><span data-progress-total>0</span> Total</span>
|
||||
</p>
|
||||
|
||||
<button
|
||||
data-popup='progress-help'
|
||||
class='flex items-center gap-1 text-sm font-medium text-gray-500 opacity-0 transition-opacity hover:text-black'
|
||||
data-progress-nums
|
||||
>
|
||||
<AstroIcon icon='question' />
|
||||
Track Progress
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p
|
||||
data-progress-nums-container
|
||||
class='striped-loader relative -mb-2 flex items-center justify-between rounded-md border bg-white bg-white px-2 py-1.5 text-sm text-sm text-gray-700 sm:hidden'
|
||||
class='relative block rounded-md border bg-white px-2 py-1.5 text-sm text-sm text-gray-700 sm:hidden striped-loader bg-white -mb-2'
|
||||
>
|
||||
<span data-progress-nums class='opacity-0 transition-opacity duration-300 text-gray-500'>
|
||||
<span data-progress-done>0</span> of <span data-progress-total>0</span> Done
|
||||
</span>
|
||||
<span data-progress-nums class='opacity-0 transition-opacity duration-300'>
|
||||
<span
|
||||
class='mr-2.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
|
||||
>
|
||||
<span data-progress-percentage>0</span>% Done
|
||||
</span>
|
||||
|
||||
<button
|
||||
data-popup='progress-help'
|
||||
class='flex items-center gap-1 text-sm font-medium text-gray-500 opacity-0 transition-opacity hover:text-black'
|
||||
data-progress-nums
|
||||
>
|
||||
<AstroIcon icon='question' />
|
||||
Track Progress
|
||||
</button>
|
||||
<span>
|
||||
<span data-progress-done>0</span> of <span data-progress-total>0</span> Done
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
import Icon from './AstroIcon.astro';
|
||||
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
|
||||
import RoadmapHint from './RoadmapHint.astro';
|
||||
import { MarkFavorite } from './FeaturedItems/MarkFavorite.tsx';
|
||||
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;
|
||||
@@ -34,7 +32,6 @@ const isRoadmapReady = !isUpcoming;
|
||||
---
|
||||
|
||||
<LoginPopup />
|
||||
<ProgressHelpPopup />
|
||||
|
||||
<div class='border-b'>
|
||||
<div class='container relative py-5 sm:py-12'>
|
||||
|
||||
@@ -18,7 +18,6 @@ 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('');
|
||||
@@ -36,6 +35,20 @@ export function TopicDetail() {
|
||||
const [resourceId, setResourceId] = useState('');
|
||||
const [resourceType, setResourceType] = useState<ResourceType>('roadmap');
|
||||
|
||||
const showLoginPopup = () => {
|
||||
const popupEl = document.querySelector(`#login-popup`);
|
||||
if (!popupEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
popupEl.classList.remove('hidden');
|
||||
popupEl.classList.add('flex');
|
||||
const focusEl = popupEl.querySelector<HTMLElement>('[autofocus]');
|
||||
if (focusEl) {
|
||||
focusEl.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// Close the topic detail when user clicks outside the topic detail
|
||||
useOutsideClick(topicRef, () => {
|
||||
setIsActive(false);
|
||||
@@ -175,6 +188,7 @@ export function TopicDetail() {
|
||||
topicId={topicId}
|
||||
resourceId={resourceId}
|
||||
resourceType={resourceType}
|
||||
onShowLoginPopup={showLoginPopup}
|
||||
onClose={() => {
|
||||
setIsActive(false);
|
||||
setIsContributing(false);
|
||||
|
||||
@@ -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<ResourceProgressType, string> = {
|
||||
};
|
||||
|
||||
export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
const { topicId, resourceId, resourceType, onClose } =
|
||||
const { topicId, resourceId, resourceType, onClose, onShowLoginPopup } =
|
||||
props;
|
||||
|
||||
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true);
|
||||
@@ -119,7 +119,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
|
||||
if (isGuest) {
|
||||
onClose();
|
||||
showLoginPopup();
|
||||
onShowLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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</span
|
||||
>
|
||||
<span class="underline mr-1">We also have a YouTube channel with visual content</span>
|
||||
<span class="underline mr-1">We also have a YouTube channel with visual content.</span>
|
||||
<span>»</span>
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/api-security.pdf'
|
||||
order: 2
|
||||
briefTitle: 'API Security'
|
||||
briefDescription: 'API Security Best Practices'
|
||||
isNew: false
|
||||
isNew: true
|
||||
isUpcoming: false
|
||||
title: 'API Security Best Practices'
|
||||
description: 'Detailed list of best practices to make your APIs secure'
|
||||
|
||||
@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/aws.pdf'
|
||||
order: 3
|
||||
briefTitle: 'AWS'
|
||||
briefDescription: 'AWS Best Practices'
|
||||
isNew: false
|
||||
isNew: true
|
||||
isUpcoming: false
|
||||
title: 'AWS Best Practices'
|
||||
description: 'Detailed list of best practices for Amazon Web Services (AWS)'
|
||||
|
||||
@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/code-review.pdf'
|
||||
order: 2
|
||||
briefTitle: 'Code Reviews'
|
||||
briefDescription: 'Code Review Best Practices'
|
||||
isNew: false
|
||||
isNew: true
|
||||
isUpcoming: false
|
||||
title: 'Code Review Best Practices'
|
||||
description: 'Detailed list of best practices for effective code reviews and quality'
|
||||
|
||||
@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/frontend-performance.pdf'
|
||||
order: 1
|
||||
briefTitle: 'Frontend Performance'
|
||||
briefDescription: 'Frontend Performance Best Practices'
|
||||
isNew: false
|
||||
isNew: true
|
||||
isUpcoming: false
|
||||
title: 'Frontend Performance Best Practices'
|
||||
description: 'Detailed list of best practices to improve your frontend performance'
|
||||
|
||||
@@ -5,5 +5,3 @@ In the ASP.NET Core framework, filters and attributes are used to add additional
|
||||
- **Filters** are classes that implement one or more of the filter interfaces provided by the framework, such as `IActionFilter`, `IResultFilter`, `IExceptionFilter`, and `IAuthorizationFilter`. Filters can be applied to controllers, action methods, or globally to the entire application. They can be used to perform tasks such as logging, caching, and handling exceptions.
|
||||
|
||||
- **Attributes** are classes that derive from `Attribute` class, and are used to decorate controllers, action methods, or properties with additional metadata. For example, the Authorize attribute can be used to require that a user is authenticated before accessing a specific action method, and the `ValidateAntiForgeryToken` attribute can be used to protect against cross-site request forgery (CSRF) attacks.
|
||||
|
||||
- [Filters](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0)
|
||||
@@ -10,4 +10,3 @@ For more resources, visit the following links:
|
||||
- [Change Tracking in EF Core](https://learn.microsoft.com/en-us/ef/core/change-tracking/)
|
||||
- [Intro to Change Tracking](https://www.oreilly.com/library/view/programming-entity-framework/9781449331825/ch05.html)
|
||||
- [ChangeTracker in Entity Framework Core](https://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspxs)
|
||||
- [ChangeTracker in Entity Framework Core](https://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx)
|
||||
@@ -11,4 +11,4 @@ Visit the following resources to learn more:
|
||||
- [HTTP/3 From A To Z: Core Concepts](https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/)
|
||||
- [HTTP/1 to HTTP/2 to HTTP/3](https://www.youtube.com/watch?v=a-sBfyiXysI)
|
||||
- [HTTP Crash Course & Exploration](https://www.youtube.com/watch?v=iYM2zFP3Zn0)
|
||||
- [SSL, TLS, HTTPS Explained](https://www.youtube.com/watch?v=j9QmMEWmcfo)
|
||||
- [SSL, TLS, HTTPS Explained](https://www.youtube.com/watch?v=j9qmmewmcfo)
|
||||
@@ -7,4 +7,3 @@ Visit the following resources to learn more:
|
||||
- [Blockchain Fork](<https://en.wikipedia.org/wiki/Fork_(blockchain)>)
|
||||
- [What is a fork?](https://www.coinbase.com/learn/crypto-basics/what-is-a-fork)
|
||||
- [What Is a Hard Fork?](https://www.investopedia.com/terms/h/hard-fork.asp)
|
||||
- [Blockchain Forking](https://www.youtube.com/watch?v=bu1gcyyfz7w)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'Computer Science'
|
||||
briefDescription: 'Curriculum with free resources for a self-taught developer.'
|
||||
title: 'Computer Science'
|
||||
description: 'Computer Science curriculum with free resources for a self-taught developer.'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -7,5 +7,4 @@ Visit the following resources to learn more:
|
||||
- [Learn Cpp](https://learncpp.com/)
|
||||
- [C++ Reference](https://en.cppreference.com/)
|
||||
- [C++ TutorialsPoint](https://www.tutorialspoint.com/cplusplus/index.htm)
|
||||
- [W3Schools C++](https://www.w3schools.com/cpp/default.asp)
|
||||
- [C++ Roadmap](https://roadmap.sh/cpp)
|
||||
- [W3Schools C++](https://www.w3schools.com/cpp/default.asp)
|
||||
@@ -8,4 +8,3 @@ Visit the following resources to learn more:
|
||||
- [Learn C - Tutorials Point](https://www.tutorialspoint.com/cprogramming/index.htm)
|
||||
- [C Programming Tutorial for Beginners](https://www.youtube.com/watch?v=KJgsSFOSQv0)
|
||||
- [Learn C Programming with Dr. Chuck](https://www.youtube.com/watch?v=j-_s8f5K30I)
|
||||
- [C Programming Full Course (Bro Code)](https://www.youtube.com/watch?v=87sh2cn0s9a)
|
||||
@@ -129,5 +129,4 @@ int main() {
|
||||
|
||||
This basic introduction to C++ should provide you with a good foundation for further learning. Explore more topics such as classes, objects, inheritance, polymorphism, templates, and the Standard Template Library (STL) to deepen your understanding of C++ and start writing more advanced programs.
|
||||
|
||||
- [LearnC++](https://www.learncpp.com/)
|
||||
- [C++ Full Course by freeCodeCamp](https://www.youtube.com/watch?v=vlnpwxzdw4y)
|
||||
- [LearnC++](https://www.learncpp.com/)
|
||||
@@ -71,6 +71,4 @@ int multiplyNumbers(int x, int y) {
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we use a function prototype for `multiplyNumbers()` before defining it. This way, we can call the function from the `main()` function even though it hasn't been defined yet in the code.
|
||||
|
||||
- [introduction to functions in c++](https://www.learncpp.com/cpp-tutorial/introduction-to-functions/)
|
||||
In this example, we use a function prototype for `multiplyNumbers()` before defining it. This way, we can call the function from the `main()` function even though it hasn't been defined yet in the code.
|
||||
@@ -27,5 +27,3 @@ Apple takes the security of iCloud very seriously and has implemented multiple l
|
||||
- **Secure Tokens**: Apple uses secure tokens for authentication, which means that your iCloud password is not stored on your devices or on Apple's servers.
|
||||
|
||||
Overall, iCloud is a convenient and secure way for Apple device users to store and synchronize their data across devices. This cloud-based service offers numerous features to ensure seamless access and enhanced protection for user data.
|
||||
|
||||
- [All about iCloud](https://www.intego.com/mac-security-blog/everything-you-can-do-with-icloud-the-complete-guide/)
|
||||
@@ -21,6 +21,3 @@ Some common DNS record types you might encounter include:
|
||||
- **TXT (Text) Record**: Contains human-readable or machine-readable text, often used for verification purposes or providing additional information about a domain.
|
||||
|
||||
As an essential part of the internet, the security and integrity of the DNS infrastructure are crucial. However, it's vulnerable to various types of cyber attacks, such as DNS cache poisoning, Distributed Denial of Service (DDoS) attacks, and DNS hijacking. Proper DNS security measures, such as DNSSEC (DNS Security Extensions) and monitoring unusual DNS traffic patterns, can help mitigate risks associated with these attacks.
|
||||
|
||||
- [DNS in detail (TryHackMe)](https://tryhackme.com/room/dnsindetail)
|
||||
- [DNS in 100 Seconds (YouTube)](https://www.youtube.com/watch?v=uvr9lhugayu)
|
||||
@@ -43,5 +43,3 @@ Exploit frameworks are essential tools in the cybersecurity landscape, as they p
|
||||
- Offers USB-based exploitation for human-interface devices
|
||||
|
||||
When using these exploit frameworks, it is important to remember that they are powerful tools that can cause significant damage if misused. Always ensure that you have explicit permission from the target organization before conducting any penetration testing activities.
|
||||
|
||||
- [Metasploit Primer (TryHackMe)](https://tryhackme.com/room/rpmetasploit)
|
||||
@@ -7,7 +7,6 @@ Here are some of the resources to learn about SSH:
|
||||
- [SSH Intro](https://www.baeldung.com/cs/ssh-intro)
|
||||
- [What is SSH?](https://www.ssh.com/academy/ssh/protocol)
|
||||
- [SFTP using SSH](https://www.goanywhere.com/blog/how-sftp-works)
|
||||
- [OpenSSH Full Guide](https://www.youtube.com/watch?v=ys5zh7kexve)
|
||||
|
||||
Visit the following to learn about SSL/TLS:
|
||||
|
||||
@@ -33,4 +32,6 @@ Here are some resources to learn about DNS:
|
||||
- [What is DNS?](https://www.cloudflare.com/en-gb/learning/dns/what-is-dns/)
|
||||
- [HOw DNS works (comic)](https://howdns.works/)
|
||||
- [DNS and How does it Work?](https://www.youtube.com/watch?v=Wj0od2ag5sk)
|
||||
- [DNS Records](https://www.youtube.com/watch?v=7lxgpKh_fRY)
|
||||
- [DNS Records](https://www.youtube.com/watch?v=7lxgpKh_fRY)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
# Azure functions
|
||||
|
||||
- [Azure Functions Overview](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview)
|
||||
# Azure functions
|
||||
@@ -1536,7 +1536,7 @@
|
||||
"x": "179",
|
||||
"y": "304",
|
||||
"properties": {
|
||||
"controlName": "ext_link:kodekloud.com/learning-path-devops-basics?aff=kamranahmed.se"
|
||||
"controlName": "ext_link:kodekloud.com?aff=kamranahmed.se"
|
||||
},
|
||||
"children": {
|
||||
"controls": {
|
||||
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'Docker'
|
||||
briefDescription: 'Step by step guide to learning Docker in 2023'
|
||||
title: 'Docker Roadmap'
|
||||
description: 'Step by step guide to learning Docker in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Inherited Widgets
|
||||
|
||||
- [InheritedWidget Official Guide](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)
|
||||
- [InheritedWidget Official Guide](https://api.flutter.dev/flutter/widgets/inheritedwidget-class.html)
|
||||
@@ -1,4 +1 @@
|
||||
# Responsive Widgets
|
||||
|
||||
- [Official flutter responsive widget ](https://docs.flutter.dev/ui/layout/adaptive-responsive)
|
||||
- [Creating responsive and adaptive apps](https://docs.flutter.dev/ui/layout/adaptive-responsive)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'Flutter'
|
||||
briefDescription: 'Step by step guide to becoming a Flutter Developer in 2023'
|
||||
title: 'Flutter Developer'
|
||||
description: 'Step by step guide to becoming a Flutter developer in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -7,4 +7,3 @@ Visit the following resources to learn more:
|
||||
- [Esbuild Official Website](https://esbuild.github.io/)
|
||||
- [Esbuild Documentation](https://esbuild.github.io/api/)
|
||||
- [Why are People Obsessed with esbuild?](https://www.youtube.com/watch?v=9XS_RA6zyyU)
|
||||
- [What Is ESBuild?](https://www.youtube.com/watch?v=zy8vu8cbwf0)
|
||||
@@ -7,4 +7,3 @@ To use the Notifications API, a web page must first request permission from the
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [Notifications API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API)
|
||||
- [Create React Notifications With the Web Notifications API](https://www.youtube.com/watch?v=mfrppinfmz0)
|
||||
@@ -9,4 +9,3 @@ Visit the following resources to learn more:
|
||||
- [Interactive HTML Course](https://github.com/denysdovhan/learnyouhtml)
|
||||
- [HTML Full Course - Build a Website Tutorial](https://www.youtube.com/watch?v=pQN-pnXPaVg)
|
||||
- [HTML Tutorial for Beginners: HTML Crash Course](https://www.youtube.com/watch?v=qz0aGYrrlhU)
|
||||
- [HTML and CSS Full Course - Beginner To Pro](https://www.youtube.com/watch?v=g3e-cpl7ofc)
|
||||
@@ -5,7 +5,6 @@ Node.js is an open-source and cross-platform JavaScript runtime environment. It
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [Official Website](https://nodejs.org/en/about/)
|
||||
- [Learn Node.js Official Website](https://nodejs.dev/en/learn/)
|
||||
- [Learn Node.js — Official Website](https://nodejs.dev/en/learn/)
|
||||
- [Node.JS Introduction](https://www.w3schools.com/nodejs/nodejs_intro.asp)
|
||||
- [Node.js and Express.js Full Course](https://www.youtube.com/watch?v=Oe421EPjeBE)
|
||||
- [Node.js Full Course](https://youtu.be/f2eqecitbl8)
|
||||
@@ -1,6 +1,6 @@
|
||||
# Deployment
|
||||
|
||||
Now that you know the basics of AWS, you should be able to deploy your application to AWS. You don't need to use all the AWS services, here is what you can probably get started with:
|
||||
Now that you know the basics of AWS, you should be able to deploy your application to AWS. You don't need to use al the AWS services, here is what you can probably get started with:
|
||||
|
||||
- Setup an EC2 instance using any AMI (e.g. latest version of Ubuntu)
|
||||
- SSH into the EC2 instance using the key pair you created
|
||||
|
||||
@@ -7,4 +7,3 @@ Visit the following resources to learn more:
|
||||
- [JSON](https://go.dev/blog/json)
|
||||
- [Guide to JSON in Golang](https://www.sohamkamani.com/golang/json/)
|
||||
- [JSON to GO](https://mholt.github.io/json-to-go/)
|
||||
- [Comprehensive Guide to using JSON in Go](https://betterstack.com/community/guides/scaling-go/json-in-go/)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'GraphQL'
|
||||
briefDescription: 'Step by Step guide to learn GraphQL in 2023'
|
||||
title: 'GraphQL'
|
||||
description: 'Step by step guide to learn GraphQL in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -13,5 +13,3 @@ Block Scope: A block is any part of JavaScript code bounded by '{}'. Variables d
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [JavaScript Scope](https://www.w3schools.com/js/js_scope.asp)
|
||||
- [javascript scope](https://wesbos.com/javascript/03-the-tricky-bits/scope)
|
||||
- [Understanding Global Local Function Block Scope](https://www.youtube.com/watch?v=_e96w6ivhng)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'JavaScript'
|
||||
briefDescription: 'Step by step guide to learn JavaScript in 2023'
|
||||
title: 'JavaScript Roadmap'
|
||||
description: 'Step by step guide to learn JavaScript in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'Kubernetes'
|
||||
briefDescription: 'Step by step guide to learning Kubernetes in 2023'
|
||||
title: 'Kubernetes Roadmap'
|
||||
description: 'Step by step guide to learning Kubernetes in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
tnsBannerLink: 'https://thenewstack.io/kubernetes?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Alert'
|
||||
dimensions:
|
||||
|
||||
@@ -39,5 +39,3 @@ NoSQL (Not only SQL) databases refer to non-relational databases, which don't fo
|
||||
## MongoDB: A NoSQL Database
|
||||
|
||||
This guide focuses on MongoDB, a popular NoSQL database that uses a document-based data model. MongoDB has been designed with flexibility, performance, and scalability in mind. With its JSON-like data format (BSON) and powerful querying capabilities, MongoDB is an excellent choice for modern applications dealing with diverse and large-scale data.
|
||||
|
||||
- [NoSQL vs. SQL Databases](https://www.mongodb.com/nosql-explained/nosql-vs-sql)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'MongoDB'
|
||||
briefDescription: 'Step by step guide to learning MongoDB in 2023'
|
||||
title: 'MongoDB Roadmap'
|
||||
description: 'Step by step guide to learning MongoDB in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -5,7 +5,7 @@ Asynchronous functions use promise behind the scenes, so understanding how promi
|
||||
Once a promise has been called, it will start in a pending state. This means that the calling function continues executing, while the promise is pending until it resolves, giving the calling function whatever data was being requested.
|
||||
|
||||
Creating a Promise:
|
||||
The Promise API exposes a Promise constructor, which you initialize using new Promise().
|
||||
The Promise API exposes a Promise constructor, which you initialize using newPromise().
|
||||
|
||||
Using resolve() and reject(), we can communicate back to the caller what the resulting Promise state was, and what to do with it.
|
||||
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
# Command line args
|
||||
|
||||
- [How To Handle Command-line Arguments in Node.js Scripts](https://www.digitalocean.com/community/tutorials/nodejs-command-line-arguments-node-scripts)
|
||||
- [Node Documentation](https://nodejs.org/docs/latest/api/process.html#processargv)
|
||||
- [Command Line Arguments ](https://youtu.be/5d7eltp0-xm)
|
||||
@@ -5,4 +5,4 @@ Playwright Test was created specifically to accommodate the needs of end-to-end
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [Official Website: Playwright](https://playwright.dev/)
|
||||
- [Playwright Tutorial](https://www.browserstack.com/guide/playwright-tutorial)
|
||||
- [Playwright Tuotorial](https://www.browserstack.com/guide/playwright-tutorial)
|
||||
|
||||
@@ -23,4 +23,4 @@ Visit the following resources to learn more:
|
||||
- [useContext Hook by Example](https://www.robinwieruch.de/react-usecontext-hook/)
|
||||
- [useReducer Hook by Example](https://www.robinwieruch.de/react-usereducer-hook/)
|
||||
- [useReducer vs useState Hook](https://www.robinwieruch.de/react-usereducer-vs-usestate/)
|
||||
- [useDefferedValue Hook video](https://www.youtube.com/watch?v=jCGMedd6IWA)
|
||||
- [useDefferedValue Hook video](https://www.youtube.com/watch?v=jcgmedd6iwa)
|
||||
@@ -5,4 +5,4 @@ Playwright Test was created specifically to accommodate the needs of end-to-end
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [Official Website: Playwright](https://playwright.dev/)
|
||||
- [Playwright Tutorial](https://www.browserstack.com/guide/playwright-tutorial)
|
||||
- [Playwright Tuotorial](https://www.browserstack.com/guide/playwright-tutorial)
|
||||
|
||||
@@ -4,6 +4,6 @@ Indentation is the practice of using whitespace to visually group related lines
|
||||
|
||||
Having a consistent indentation and code style can help to make the code more readable and understandable, which can improve the maintainability of the system.
|
||||
|
||||
Learn more from the following links:
|
||||
Learn mor from the following links:
|
||||
|
||||
- [Clean Code – Formatting](https://www.baeldung.com/cs/clean-code-formatting)
|
||||
|
||||
@@ -7,7 +7,7 @@ briefDescription: 'Step by step guide to becoming a Spring Boot Developer in 202
|
||||
title: 'Spring Boot Developer'
|
||||
description: 'Step by step guide to becoming a Spring Boot developer in 2023'
|
||||
hasTopics: true
|
||||
isNew: false
|
||||
isNew: true
|
||||
dimensions:
|
||||
width: 968
|
||||
height: 1245.52
|
||||
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'System Design'
|
||||
briefDescription: 'Guide to learn system Design'
|
||||
title: 'System Design'
|
||||
description: 'Everything you need to know about designing large scale systems.'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -7,7 +7,7 @@ briefDescription: 'Everything you need to learn about TypeScript in 2023'
|
||||
title: 'TypeScript'
|
||||
description: 'Everything you need to learn about TypeScript in 2023'
|
||||
hasTopics: true
|
||||
isNew: false
|
||||
isNew: true
|
||||
dimensions:
|
||||
width: 968
|
||||
height: 1884.38
|
||||
|
||||
@@ -26,5 +26,3 @@ To enhance user decision-making experience, consider the following principles:
|
||||
- **Defaults**: Use smart defaults to help users make decisions in a context-appropriate manner, which could range from pre-filled forms to suggested options.
|
||||
|
||||
Understanding human decision making and incorporating these principles into UX design will enable designers to create more intuitive, enjoyable, and efficient user experiences.
|
||||
|
||||
- [Types of Cognitive Bias](https://www.youtube.com/watch?v=wewgbir_riw)
|
||||
@@ -10,5 +10,3 @@ In the context of UX design, nudge theory can be applied in various ways to infl
|
||||
- **Timely interventions**: Providing guidance or prompts at the right moment in the user's journey. For example, offering help when a user encounters a complex task or reminding them of the benefits of completing a process when their motivation may be wavering.
|
||||
|
||||
When applying nudge theory in UX design, it's crucial to maintain a balance between encouraging positive behaviors and respecting user autonomy. Designers should aim to empower users with meaningful choices and useful information, rather than manipulating or deceiving them.
|
||||
|
||||
- [Nudge Theory Explained with Examples (on YouTube)](https://www.youtube.com/watch?v=u3yxxteiyya&ab_channel=epm)
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'UX Design'
|
||||
briefDescription: 'Step by step guide to becoming a UX Designer in 2023'
|
||||
title: 'UX Design'
|
||||
description: 'Step by step guide to becoming a UX Designer in 2023'
|
||||
isNew: false
|
||||
isNew: true
|
||||
hasTopics: true
|
||||
dimensions:
|
||||
width: 968
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export default async (load, opts) => {
|
||||
const isAuthenticated = document.cookie.toString().indexOf('__roadmapsh_jt__') !== -1;
|
||||
if (isAuthenticated) {
|
||||
console.log("loading");
|
||||
|
||||
const hydrate = await load();
|
||||
await hydrate();
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm1 16h-2v-2h2v2zm.976-4.885c-.196.158-.385.309-.535.459-.408.407-.44.777-.441.793v.133h-2v-.167c0-.118.029-1.177 1.026-2.174.195-.195.437-.393.691-.599.734-.595 1.216-1.029 1.216-1.627a1.934 1.934 0 0 0-3.867.001h-2C8.066 7.765 9.831 6 12 6s3.934 1.765 3.934 3.934c0 1.597-1.179 2.55-1.958 3.181z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 535 B |
@@ -1,6 +1,5 @@
|
||||
---
|
||||
import Analytics from '../components/Analytics/Analytics.astro';
|
||||
import LoginPopup from '../components/AuthenticationFlow/LoginPopup.astro';
|
||||
import Authenticator from '../components/Authenticator/Authenticator.astro';
|
||||
import { CommandMenu } from '../components/CommandMenu/CommandMenu';
|
||||
import Footer from '../components/Footer.astro';
|
||||
@@ -150,7 +149,6 @@ const gaPageIdentifier = Astro.url.pathname
|
||||
</slot>
|
||||
|
||||
<Authenticator />
|
||||
<LoginPopup />
|
||||
<CommandMenu client:idle />
|
||||
<PageProgress initialMessage={initialLoadingMessage} client:idle />
|
||||
<PageSponsor
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export function showLoginPopup() {
|
||||
const popupEl = document.querySelector(`#login-popup`);
|
||||
if (!popupEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
popupEl.classList.remove('hidden');
|
||||
popupEl.classList.add('flex');
|
||||
|
||||
const focusEl = popupEl.querySelector<HTMLElement>('[autofocus]');
|
||||
if (focusEl) {
|
||||
focusEl.focus();
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
blockquote p:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user