mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
feat: replace async renderer (#9253)
* feat: replace async renderer * fix: balsamiq font
This commit is contained in:
@@ -113,13 +113,21 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
|
||||
onStream: async (result) => {
|
||||
const roadmap = generateAICourseRoadmapStructure(result, true);
|
||||
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
|
||||
const svg = await renderFlowJSON({ nodes, edges });
|
||||
const svg = renderFlowJSON({ nodes, edges });
|
||||
// it will be null only on server side
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
replaceChildren(containerEl.current!, svg);
|
||||
},
|
||||
onStreamEnd: async (result) => {
|
||||
const roadmap = generateAICourseRoadmapStructure(result, true);
|
||||
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
|
||||
const svg = await renderFlowJSON({ nodes, edges });
|
||||
const svg = renderFlowJSON({ nodes, edges });
|
||||
// it will be null only on server side
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
replaceChildren(containerEl.current!, svg);
|
||||
setRoadmapStructure(roadmap);
|
||||
setIsGenerating(false);
|
||||
@@ -217,7 +225,7 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden mx-auto min-h-[500px] rounded-xl border border-gray-200 bg-white shadow-xs lg:max-w-5xl">
|
||||
<div className="relative mx-auto min-h-[500px] overflow-hidden rounded-xl border border-gray-200 bg-white shadow-xs lg:max-w-5xl">
|
||||
<AICourseOutlineHeader
|
||||
course={course}
|
||||
isLoading={isLoading}
|
||||
@@ -237,7 +245,9 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoggedIn() && <LoginToView className="-mt-1 -mb-2 rounded-none border-none" />}
|
||||
{!isLoggedIn() && (
|
||||
<LoginToView className="-mt-1 -mb-2 rounded-none border-none" />
|
||||
)}
|
||||
|
||||
{error && !isGenerating && !isLoggedIn() && (
|
||||
<div className="absolute inset-0 flex h-full w-full flex-col items-center justify-center">
|
||||
|
||||
@@ -133,13 +133,16 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
|
||||
const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
|
||||
|
||||
const renderRoadmap = async (roadmap: string) => {
|
||||
const renderRoadmap = (roadmap: string) => {
|
||||
const result = generateAICourseRoadmapStructure(roadmap);
|
||||
const { nodes, edges } = generateAIRoadmapFromText(result);
|
||||
const svg = await renderFlowJSON({ nodes, edges });
|
||||
if (roadmapContainerRef?.current) {
|
||||
replaceChildren(roadmapContainerRef?.current, svg);
|
||||
const svg = renderFlowJSON({ nodes, edges });
|
||||
const container = roadmapContainerRef?.current;
|
||||
if (!svg || !container) {
|
||||
return;
|
||||
}
|
||||
|
||||
replaceChildren(container, svg);
|
||||
};
|
||||
|
||||
const loadTermRoadmap = async (term: string) => {
|
||||
@@ -221,7 +224,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
});
|
||||
}
|
||||
|
||||
await renderRoadmap(result);
|
||||
renderRoadmap(result);
|
||||
},
|
||||
onStreamEnd: async (result) => {
|
||||
result = result
|
||||
@@ -358,7 +361,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
}
|
||||
|
||||
const { term, title, data } = response;
|
||||
await renderRoadmap(data);
|
||||
renderRoadmap(data);
|
||||
|
||||
setCurrentRoadmap({
|
||||
id: roadmapId,
|
||||
@@ -557,7 +560,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
<span>
|
||||
<span
|
||||
className={cn(
|
||||
'mr-0.5 inline-block rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800',
|
||||
'mr-0.5 inline-block rounded-xl border px-1.5 text-center text-sm text-gray-800 tabular-nums',
|
||||
{
|
||||
'animate-pulse border-zinc-300 bg-zinc-300 text-zinc-300':
|
||||
!roadmapLimit,
|
||||
@@ -652,7 +655,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<button
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-yellow-400 py-1.5 pl-2.5 pr-3 text-xs font-medium transition-opacity duration-300 hover:bg-yellow-500 sm:text-sm"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-yellow-400 py-1.5 pr-3 pl-2.5 text-xs font-medium transition-opacity duration-300 hover:bg-yellow-500 sm:text-sm"
|
||||
onClick={downloadGeneratedRoadmapContent}
|
||||
>
|
||||
<Download size={15} />
|
||||
@@ -668,7 +671,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<button
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:text-sm"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pr-3 pl-2.5 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:text-sm"
|
||||
onClick={async () => {
|
||||
const response = await saveAIRoadmap();
|
||||
if (response?.roadmapSlug) {
|
||||
@@ -685,7 +688,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="hidden items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:inline-flex sm:text-sm"
|
||||
className="hidden items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pr-3 pl-2.5 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:inline-flex sm:text-sm"
|
||||
onClick={async () => {
|
||||
const response = await saveAIRoadmap();
|
||||
if (response?.roadmapId) {
|
||||
@@ -718,7 +721,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
className="relative min-h-[400px] px-4 py-5 [&>svg]:mx-auto [&>svg]:max-w-[1300px]"
|
||||
/>
|
||||
{!isAuthenticatedUser && (
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className="absolute right-0 bottom-0 left-0">
|
||||
<div className="h-80 w-full bg-linear-to-t from-gray-100 to-transparent" />
|
||||
<div className="bg-gray-100">
|
||||
<div className="mx-auto max-w-[600px] flex-col items-center justify-center bg-gray-100 px-5 pt-px">
|
||||
@@ -726,7 +729,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
|
||||
<h2 className="mb-0.5 text-xl font-medium sm:mb-3 sm:text-2xl">
|
||||
Sign up to View the full roadmap
|
||||
</h2>
|
||||
<p className="mb-6 text-balance text-sm text-gray-600 sm:text-base">
|
||||
<p className="mb-6 text-sm text-balance text-gray-600 sm:text-base">
|
||||
You must be logged in to view the complete roadmap
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,18 @@ import './ChatRoadmapRenderer.css';
|
||||
|
||||
import { lazy, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
renderResourceProgress,
|
||||
updateResourceProgress,
|
||||
type ResourceProgressType,
|
||||
renderTopicProgress,
|
||||
refreshProgressCounters,
|
||||
} from '../../lib/resource-progress';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import type { Edge, Node } from '@roadmapsh/editor';
|
||||
import { slugify } from '../../lib/slugger';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { userResourceProgressOptions } from '../../queries/resource-progress';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { TopicResourcesModal } from './TopicResourcesModal';
|
||||
|
||||
const Renderer = lazy(() =>
|
||||
import('@roadmapsh/editor').then((mod) => ({
|
||||
|
||||
@@ -136,7 +136,7 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
}, [roadmapDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!roadmapDetail || !roadmapContainerRef.current) {
|
||||
if (!roadmapDetail || !roadmapContainerRef.current || !roadmapDetail.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,12 +108,17 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
const json = await res.json();
|
||||
const svg =
|
||||
renderer === 'editor'
|
||||
? await renderFlowJSON(json)
|
||||
? renderFlowJSON(json)
|
||||
: await wireframeJSONToSVG(json, {
|
||||
fontURL: '/fonts/balsamiq.woff2',
|
||||
});
|
||||
|
||||
replaceChildren(containerEl.current!, svg);
|
||||
const container = containerEl.current;
|
||||
if (!svg || !container) {
|
||||
return;
|
||||
}
|
||||
|
||||
replaceChildren(container, svg);
|
||||
}
|
||||
|
||||
useKeydown('Escape', () => {
|
||||
|
||||
@@ -101,7 +101,7 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
}
|
||||
|
||||
return renderer === 'editor'
|
||||
? await renderFlowJSON(roadmapJson as any)
|
||||
? (renderFlowJSON(roadmapJson as any) as SVGElement)
|
||||
: await wireframeJSONToSVG(roadmapJson, {
|
||||
fontURL: '/fonts/balsamiq.woff2',
|
||||
});
|
||||
@@ -210,7 +210,7 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
return (
|
||||
<div
|
||||
id={'user-progress-modal'}
|
||||
className="fixed left-0 right-0 top-0 z-100 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50"
|
||||
className="fixed top-0 right-0 left-0 z-100 h-full items-center justify-center overflow-x-hidden overflow-y-auto overscroll-contain bg-black/50"
|
||||
>
|
||||
<div className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
|
||||
<div
|
||||
@@ -230,7 +230,7 @@ export function UserProgressModal(props: ProgressMapProps) {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-gray-100 bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden`}
|
||||
className={`absolute top-3 right-2.5 ml-auto inline-flex items-center rounded-lg bg-gray-100 bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
||||
@@ -40,7 +40,11 @@ export function aiRoadmapOptions(roadmapSlug?: string) {
|
||||
|
||||
const result = generateAICourseRoadmapStructure(res.data);
|
||||
const { nodes, edges } = generateAIRoadmapFromText(result);
|
||||
const svg = await renderFlowJSON({ nodes, edges });
|
||||
const svg = renderFlowJSON({ nodes, edges });
|
||||
if (!svg) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const svgHtml = svg.outerHTML;
|
||||
|
||||
return {
|
||||
@@ -153,7 +157,11 @@ export async function generateAIRoadmap(options: GenerateAIRoadmapOptions) {
|
||||
onMessage: async (message) => {
|
||||
const result = generateAICourseRoadmapStructure(message);
|
||||
const { nodes, edges } = generateAIRoadmapFromText(result);
|
||||
const svg = await renderFlowJSON({ nodes, edges });
|
||||
const svg = renderFlowJSON({ nodes, edges });
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
onRoadmapSvgChange?.(svg);
|
||||
},
|
||||
onMessageEnd: async () => {
|
||||
|
||||
@@ -23,7 +23,13 @@ export function roadmapJSONOptions(roadmapId: string) {
|
||||
`${baseUrl}/${roadmapId}.json`,
|
||||
);
|
||||
|
||||
const svg = await renderFlowJSON(roadmapJSON);
|
||||
const svg = renderFlowJSON(roadmapJSON);
|
||||
if (!svg) {
|
||||
return {
|
||||
json: roadmapJSON,
|
||||
svg: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
json: roadmapJSON,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@plugin 'tailwind-scrollbar';
|
||||
|
||||
@font-face {
|
||||
font-family: 'Balsamiq Sans';
|
||||
font-family: 'balsamiq';
|
||||
src: url('/fonts/balsamiq.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
@theme {
|
||||
--font-balsamiq: 'Balsamiq Sans', sans-serif;
|
||||
--font-balsamiq: 'balsamiq', sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user