mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-29 20:21:50 +02:00
Handle rendering of the roadmap topics
This commit is contained in:
@@ -4,7 +4,8 @@ import TopicOverlay from '../TopicOverlay/TopicOverlay.astro';
|
|||||||
import './FrameRenderer.css';
|
import './FrameRenderer.css';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
roadmapId: string;
|
resourceType: 'roadmap' | 'best-practice';
|
||||||
|
resourceId: string;
|
||||||
jsonUrl: string;
|
jsonUrl: string;
|
||||||
dimensions?: {
|
dimensions?: {
|
||||||
width: number;
|
width: number;
|
||||||
@@ -12,13 +13,14 @@ export interface Props {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { roadmapId, jsonUrl, dimensions = null } = Astro.props;
|
const { resourceId, resourceType, jsonUrl, dimensions = null } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id='roadmap-svg'
|
id='resource-svg'
|
||||||
style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
|
style={dimensions ? `--aspect-ratio:${dimensions.width}/${dimensions.height}` : null}
|
||||||
data-roadmap-id={roadmapId}
|
data-resource-type={resourceType}
|
||||||
|
data-resource-id={resourceId}
|
||||||
data-json-url={jsonUrl}
|
data-json-url={jsonUrl}
|
||||||
>
|
>
|
||||||
<Loader />
|
<Loader />
|
||||||
|
@@ -1,23 +1,17 @@
|
|||||||
import { wireframeJSONToSVG } from 'roadmap-renderer';
|
import { wireframeJSONToSVG } from 'roadmap-renderer';
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{ roadmapId: string, jsonUrl: string }} RoadmapConfig
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
/**
|
|
||||||
* @param {RoadmapConfig} config
|
|
||||||
*/
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.roadmapId = '';
|
this.resourceId = '';
|
||||||
|
this.resourceType = '';
|
||||||
this.jsonUrl = '';
|
this.jsonUrl = '';
|
||||||
|
|
||||||
this.containerId = 'roadmap-svg';
|
this.containerId = 'resource-svg';
|
||||||
|
|
||||||
this.init = this.init.bind(this);
|
this.init = this.init.bind(this);
|
||||||
this.onDOMLoaded = this.onDOMLoaded.bind(this);
|
this.onDOMLoaded = this.onDOMLoaded.bind(this);
|
||||||
this.fetchRoadmapSvg = this.fetchRoadmapSvg.bind(this);
|
this.jsonToSvg = this.jsonToSvg.bind(this);
|
||||||
this.handleRoadmapClick = this.handleRoadmapClick.bind(this);
|
this.handleSvgClick = this.handleSvgClick.bind(this);
|
||||||
this.prepareConfig = this.prepareConfig.bind(this);
|
this.prepareConfig = this.prepareConfig.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +26,8 @@ export class Renderer {
|
|||||||
|
|
||||||
const dataset = this.containerEl.dataset;
|
const dataset = this.containerEl.dataset;
|
||||||
|
|
||||||
this.roadmapId = dataset.roadmapId;
|
this.resourceType = dataset.resourceType;
|
||||||
|
this.resourceId = dataset.resourceId;
|
||||||
this.jsonUrl = dataset.jsonUrl;
|
this.jsonUrl = dataset.jsonUrl;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -42,7 +37,7 @@ export class Renderer {
|
|||||||
* @param { string } jsonUrl
|
* @param { string } jsonUrl
|
||||||
* @returns {Promise<SVGElement>}
|
* @returns {Promise<SVGElement>}
|
||||||
*/
|
*/
|
||||||
fetchRoadmapSvg(jsonUrl) {
|
jsonToSvg(jsonUrl) {
|
||||||
if (!jsonUrl) {
|
if (!jsonUrl) {
|
||||||
console.error('jsonUrl not defined in frontmatter');
|
console.error('jsonUrl not defined in frontmatter');
|
||||||
return null;
|
return null;
|
||||||
@@ -64,14 +59,14 @@ export class Renderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchRoadmapSvg(this.jsonUrl)
|
this.jsonToSvg(this.jsonUrl)
|
||||||
.then((svg) => {
|
.then((svg) => {
|
||||||
document.getElementById(this.containerId).replaceChildren(svg);
|
document.getElementById(this.containerId).replaceChildren(svg);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRoadmapClick(e) {
|
handleSvgClick(e) {
|
||||||
const targetGroup = e.target.closest('g') || {};
|
const targetGroup = e.target.closest('g') || {};
|
||||||
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
|
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
@@ -80,11 +75,19 @@ export class Renderer {
|
|||||||
|
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
if (/^ext_link/.test(groupId)) {
|
||||||
|
window.open(`https://${groupId.replace('ext_link:', '')}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sorting prefix from groupId
|
||||||
|
const normalizedGroupId = groupId.replace(/^\d+-/, '');
|
||||||
|
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent('topic.click', {
|
new CustomEvent(`${this.resourceType}.topic.click`, {
|
||||||
detail: {
|
detail: {
|
||||||
topicId: groupId,
|
topicId: normalizedGroupId,
|
||||||
roadmapId: this.roadmapId,
|
resourceId: this.resourceId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -92,7 +95,7 @@ export class Renderer {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
|
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
|
||||||
window.addEventListener('click', this.handleRoadmapClick);
|
window.addEventListener('click', this.handleSvgClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,10 +10,12 @@ export class Topic {
|
|||||||
this.closeTopicId = 'close-topic';
|
this.closeTopicId = 'close-topic';
|
||||||
this.contributionTextId = 'contrib-meta';
|
this.contributionTextId = 'contrib-meta';
|
||||||
|
|
||||||
this.activeRoadmapId = null;
|
this.activeResourceType = null;
|
||||||
|
this.activeResourceId = null;
|
||||||
this.activeTopicId = null;
|
this.activeTopicId = null;
|
||||||
|
|
||||||
this.handleTopicClick = this.handleTopicClick.bind(this);
|
this.handleRoadmapTopicClick = this.handleRoadmapTopicClick.bind(this);
|
||||||
|
this.handleBestPracticeTopicClick = this.handleBestPracticeTopicClick.bind(this);
|
||||||
|
|
||||||
this.close = this.close.bind(this);
|
this.close = this.close.bind(this);
|
||||||
this.resetDOM = this.resetDOM.bind(this);
|
this.resetDOM = this.resetDOM.bind(this);
|
||||||
@@ -86,7 +88,7 @@ export class Topic {
|
|||||||
close() {
|
close() {
|
||||||
this.resetDOM(true);
|
this.resetDOM(true);
|
||||||
|
|
||||||
this.activeRoadmapId = null;
|
this.activeResourceId = null;
|
||||||
this.activeTopicId = null;
|
this.activeTopicId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +117,8 @@ export class Topic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchTopicHtml(roadmapId, topicId) {
|
renderTopicFromUrl(url) {
|
||||||
const topicPartial = topicId.replace(/^\d+-/, '').replaceAll(/:/g, '/');
|
return fetch(url)
|
||||||
const fullUrl = `/${roadmapId}/${topicPartial}`;
|
|
||||||
|
|
||||||
return fetch(fullUrl)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return res.text();
|
return res.text();
|
||||||
})
|
})
|
||||||
@@ -129,26 +128,7 @@ export class Topic {
|
|||||||
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
|
const node = new DOMParser().parseFromString(topicHtml, 'text/html');
|
||||||
|
|
||||||
return node.getElementById('main-content');
|
return node.getElementById('main-content');
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
handleTopicClick(e) {
|
|
||||||
const { roadmapId, topicId } = e.detail;
|
|
||||||
if (!topicId || !roadmapId) {
|
|
||||||
console.log('Missing topic or roadmap: ', e.detail);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeRoadmapId = roadmapId;
|
|
||||||
this.activeTopicId = topicId;
|
|
||||||
|
|
||||||
if (/^ext_link/.test(topicId)) {
|
|
||||||
window.open(`https://${topicId.replace('ext_link:', '')}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetDOM();
|
|
||||||
this.fetchTopicHtml(roadmapId, topicId)
|
|
||||||
.then((content) => {
|
.then((content) => {
|
||||||
this.populate(content);
|
this.populate(content);
|
||||||
})
|
})
|
||||||
@@ -158,6 +138,37 @@ export class Topic {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
alert('Best practices are not yet implemented!');
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
this.renderTopicFromUrl(`/${roadmapId}/${topicId.replaceAll(':', '/')}`);
|
||||||
|
}
|
||||||
|
|
||||||
queryRoadmapElementsByTopicId(topicId) {
|
queryRoadmapElementsByTopicId(topicId) {
|
||||||
const elements = document.querySelectorAll(`[data-group-id$="-${topicId}"]`);
|
const elements = document.querySelectorAll(`[data-group-id$="-${topicId}"]`);
|
||||||
const matchingElements = [];
|
const matchingElements = [];
|
||||||
@@ -219,7 +230,8 @@ export class Topic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener('topic.click', this.handleTopicClick);
|
window.addEventListener('roadmap.topic.click', this.handleRoadmapTopicClick);
|
||||||
|
window.addEventListener('best-practice.topic.click', this.handleBestPracticeTopicClick);
|
||||||
window.addEventListener('click', this.handleOverlayClick);
|
window.addEventListener('click', this.handleOverlayClick);
|
||||||
window.addEventListener('contextmenu', this.rightClickListener);
|
window.addEventListener('contextmenu', this.rightClickListener);
|
||||||
|
|
||||||
|
@@ -79,7 +79,12 @@ const contentContributionLink = `https://github.com/kamranahmedse/developer-road
|
|||||||
<ShareIcons description={roadmapData.featuredDescription} pageUrl={`https://roadmap.sh/${roadmapId}`} />
|
<ShareIcons description={roadmapData.featuredDescription} pageUrl={`https://roadmap.sh/${roadmapId}`} />
|
||||||
<TopicOverlay contentContributionLink={contentContributionLink} />
|
<TopicOverlay contentContributionLink={contentContributionLink} />
|
||||||
|
|
||||||
<FrameRenderer roadmapId={roadmapId} jsonUrl={roadmapData.jsonUrl} dimensions={roadmapData.dimensions} />
|
<FrameRenderer
|
||||||
|
resourceType={'roadmap'}
|
||||||
|
resourceId={roadmapId}
|
||||||
|
jsonUrl={roadmapData.jsonUrl}
|
||||||
|
dimensions={roadmapData.dimensions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -75,7 +75,8 @@ const contentContributionLink = `https://github.com/kamranahmedse/developer-road
|
|||||||
<TopicOverlay contentContributionLink={contentContributionLink} />
|
<TopicOverlay contentContributionLink={contentContributionLink} />
|
||||||
|
|
||||||
<FrameRenderer
|
<FrameRenderer
|
||||||
roadmapId={bestPracticeId}
|
resourceType={'best-practice'}
|
||||||
|
resourceId={bestPracticeId}
|
||||||
jsonUrl={bestPracticeData.jsonUrl}
|
jsonUrl={bestPracticeData.jsonUrl}
|
||||||
dimensions={bestPracticeData.dimensions}
|
dimensions={bestPracticeData.dimensions}
|
||||||
/>
|
/>
|
||||||
|
Reference in New Issue
Block a user