feat: xyflow upgrade (#7803)

* wip

* fix: reset the sizes

* fix: update zustand

* fix: update

* fix: add additional width

* wip

* fix: remove hacky code

* wip

* wip

* wip

* wip

* wip

* fix: try pre-commit

* fix: add check pre-commit

* fix: remove xyflow

* fix: remove unnecessary files

* fix: update packages

* Update scripts/generate-renderer.sh

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
pull/8455/head
Arik Chakma 2 weeks ago committed by GitHub
parent dc2142dde0
commit 2485b716dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .gitignore
  2. 5
      astro.config.mjs
  3. 14
      editor/readonly-editor.tsx
  4. 14
      editor/renderer/index.tsx
  5. 5
      editor/renderer/renderer.ts
  6. 5
      package.json
  7. 3110
      pnpm-lock.yaml
  8. 14
      renderer/index.tsx
  9. 5
      renderer/renderer.ts
  10. 2
      scripts/editor-roadmap-content-json.ts
  11. 2
      scripts/editor-roadmap-content.ts
  12. 2
      scripts/editor-roadmap-dirs.ts
  13. 33
      scripts/generate-renderer.sh
  14. 76
      scripts/migrate-editor-roadmap.ts
  15. 4
      src/components/CustomRoadmap/FlowRoadmapRenderer.tsx
  16. 2
      src/components/Dashboard/DashboardPage.tsx
  17. 2
      src/components/EditorRoadmap/EditorRoadmap.tsx
  18. 12
      src/components/EditorRoadmap/EditorRoadmapRenderer.tsx
  19. 4
      src/components/GenerateCourse/AICourseRoadmapView.tsx
  20. 10
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  21. 2
      src/components/SQLCourse/ReviewsSection.tsx
  22. 7
      src/components/TeamProgress/MemberCustomProgressModal.tsx
  23. 2
      src/components/TeamProgress/MemberProgressModal.tsx
  24. 6
      src/components/UserProgress/UserCustomProgressModal.tsx
  25. 2
      src/components/UserProgress/UserProgressModal.tsx
  26. 4
      src/components/UserPublicProfile/UserProfileRoadmapRenderer.tsx
  27. 4
      src/layouts/BaseLayout.astro
  28. 21
      src/styles/global.css

7
.gitignore vendored

@ -28,9 +28,4 @@ pnpm-debug.log*
/playwright-report/
/playwright/.cache/
tests-examples
*.csv
/editor/*
!/editor/readonly-editor.tsx
!/editor/renderer/renderer.ts
!/editor/renderer/index.tsx
*.csv

@ -72,4 +72,9 @@ export default defineConfig({
}),
react(),
],
vite: {
ssr: {
noExternal: [/^@roadmapsh\/editor.*$/],
},
},
});

@ -1,14 +0,0 @@
export function ReadonlyEditor(props: any) {
return (
<div className="fixed bottom-0 left-0 right-0 top-0 z-[9999] border bg-white p-5 text-black">
<h2 className="mb-2 text-xl font-semibold">Private Component</h2>
<p className="mb-4">
Renderer is a private component. If you are a collaborator and have
access to it. Run the following command:
</p>
<code className="mt-5 rounded-md bg-gray-800 p-2 text-white">
npm run generate-renderer
</code>
</div>
);
}

@ -1,14 +0,0 @@
export function Renderer(props: any) {
return (
<div className="fixed bottom-0 left-0 right-0 top-0 z-[9999] border bg-white p-5 text-black">
<h2 className="mb-2 text-xl font-semibold">Private Component</h2>
<p className="mb-4">
Renderer is a private component. If you are a collaborator and have
access to it. Run the following command:
</p>
<code className="mt-5 rounded-md bg-gray-800 p-2 text-white">
npm run generate-renderer
</code>
</div>
);
}

@ -1,5 +0,0 @@
export function renderFlowJSON(data: any, options?: any) {
console.warn("renderFlowJSON is not implemented");
console.warn("run the following command to generate the renderer:");
console.warn("> npm run generate-renderer");
}

@ -26,6 +26,7 @@
"warm:urls": "sh ./scripts/warm-urls.sh https://roadmap.sh/sitemap-0.xml",
"compress:images": "tsx ./scripts/compress-images.ts",
"generate:roadmap-content-json": "tsx ./scripts/editor-roadmap-content-json.ts",
"migrate:editor-roadmaps": "tsx ./scripts/migrate-editor-roadmap.ts",
"test:e2e": "playwright test"
},
"dependencies": {
@ -38,6 +39,7 @@
"@nanostores/react": "^0.8.0",
"@napi-rs/image": "^1.9.2",
"@resvg/resvg-js": "^2.6.2",
"@roadmapsh/editor": "npm:@roadmapsh/dummy-editor@^0.0.5",
"@tanstack/react-query": "^5.59.16",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
@ -67,7 +69,6 @@
"react-resizable-panels": "^2.1.7",
"react-textarea-autosize": "^8.5.7",
"react-tooltip": "^5.28.0",
"reactflow": "^11.11.4",
"rehype-external-links": "^3.0.0",
"remark-parse": "^11.0.0",
"roadmap-renderer": "^1.0.6",
@ -82,7 +83,7 @@
"tiptap-markdown": "^0.8.10",
"turndown": "^7.2.0",
"unified": "^11.0.5",
"zustand": "^4.5.5"
"zustand": "^5.0.1"
},
"devDependencies": {
"@ai-sdk/google": "^1.1.19",

File diff suppressed because it is too large Load Diff

@ -1,14 +0,0 @@
export function Renderer(props: any) {
return (
<div className="fixed bottom-0 left-0 right-0 top-0 z-[9999] border bg-white p-5 text-black">
<h2 className="mb-2 text-xl font-semibold">Private Component</h2>
<p className="mb-4">
Renderer is a private component. If you are a collaborator and have
access to it. Run the following command:
</p>
<code className="mt-5 rounded-md bg-gray-800 p-2 text-white">
npm run generate-renderer
</code>
</div>
);
}

@ -1,5 +0,0 @@
export function renderFlowJSON(data: any, options?: any) {
console.warn("renderFlowJSON is not implemented");
console.warn("run the following command to generate the renderer:");
console.warn("> npm run generate-renderer");
}

@ -1,7 +1,7 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Node } from 'reactflow';
import type { Node } from '@roadmapsh/editor';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger';

@ -1,7 +1,7 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Edge, Node } from 'reactflow';
import type { Edge, Node } from '@roadmapsh/editor';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger';

@ -1,7 +1,7 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Node } from 'reactflow';
import type { Node } from '@roadmapsh/editor';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger';

@ -2,33 +2,10 @@
set -e
# ignore cloning if .temp/web-draw already exists
if [ ! -d ".temp/web-draw" ]; then
mkdir -p .temp
git clone git@github.com:roadmapsh/web-draw.git .temp/web-draw
fi
# Fetch the latest commit hash from the GitHub repo
LATEST_COMMIT_HASH=$(git ls-remote https://github.com/roadmapsh/web-draw-v2.git refs/heads/main | awk '{print $1}')
rm -rf editor
mkdir editor
echo "Latest commit hash: $LATEST_COMMIT_HASH"
# copy the files at /src/editor/* to /editor
# while replacing any existing files
cp -rf .temp/web-draw/src/editor/* editor
# Add @ts-nocheck to the top of each ts and tsx file
# so that the typescript compiler doesn't complain
# about the missing types
find editor -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 | while IFS= read -r -d '' file; do
if [ -f "$file" ]; then
echo "// @ts-nocheck" > temp
cat "$file" >> temp
mv temp "$file"
echo "Added @ts-nocheck to $file"
fi
done
# ignore the worktree changes for the editor directory
git update-index --assume-unchanged editor/readonly-editor.tsx || true
git update-index --assume-unchanged editor/renderer/index.tsx || true
git update-index --assume-unchanged editor/renderer/renderer.ts || true
# Install the package using the latest commit hash
pnpm add github:roadmapsh/web-draw#"$LATEST_COMMIT_HASH"\&path:packages/editor

@ -0,0 +1,76 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Node } from '@roadmapsh/editor';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Directory containing the roadmaps
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
const allRoadmaps = await fs.readdir(ROADMAP_CONTENT_DIR);
const editorRoadmapIds = new Set<string>();
for (const roadmapId of allRoadmaps) {
const roadmapFrontmatterDir = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
`${roadmapId}.md`,
);
const roadmapFrontmatterRaw = await fs.readFile(
roadmapFrontmatterDir,
'utf-8',
);
const { data } = matter(roadmapFrontmatterRaw);
const roadmapFrontmatter = data as RoadmapFrontmatter;
if (roadmapFrontmatter.renderer === 'editor') {
editorRoadmapIds.add(roadmapId);
}
}
for (const roadmapId of editorRoadmapIds) {
const roadmapJSONDir = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
`${roadmapId}.json`,
);
const roadmapJSONRaw = await fs.readFile(roadmapJSONDir, 'utf-8');
const roadmapJSON = JSON.parse(roadmapJSONRaw);
const roadmapNodes = roadmapJSON.nodes as Node[];
const updatedNodes = roadmapNodes.map((node) => {
const width = +(node?.width || node?.style?.width || 0);
const height = +(node?.height || node?.style?.height || 0);
const ADDITIONAL_WIDTH = 1;
// adding one `1px` in width to avoid the node to be cut in half
// this is a quick fix to avoid the issue
if (node?.style?.width) {
node.style.width = width + ADDITIONAL_WIDTH;
}
if (node?.width) {
node.width = width + ADDITIONAL_WIDTH;
}
return {
...node,
measured: {
width: width + ADDITIONAL_WIDTH,
height,
},
};
});
const updatedRoadmapJSON = {
...roadmapJSON,
nodes: updatedNodes,
};
const updatedRoadmapJSONString = JSON.stringify(updatedRoadmapJSON, null, 2);
await fs.writeFile(roadmapJSONDir, updatedRoadmapJSONString, 'utf-8');
}

@ -1,4 +1,4 @@
import { ReadonlyEditor } from '../../../editor/readonly-editor';
import { ReadonlyEditor } from '@roadmapsh/editor';
import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
import {
refreshProgressCounters,
@ -9,7 +9,7 @@ import {
} from '../../lib/resource-progress';
import { pageProgressMessage } from '../../stores/page';
import { useToast } from '../../hooks/use-toast';
import type { Node } from 'reactflow';
import type { Node } from '@roadmapsh/editor';
import { type MouseEvent, useCallback, useRef, useState } from 'react';
import { EmptyRoadmap } from './EmptyRoadmap';
import { cn } from '../../lib/classname';

@ -1,6 +1,5 @@
import { useStore } from '@nanostores/react';
import { useEffect, useState } from 'react';
import { cn } from '../../../editor/utils/classname';
import { useParams } from '../../hooks/use-params';
import { useToast } from '../../hooks/use-toast';
import { httpGet } from '../../lib/http';
@ -13,6 +12,7 @@ import { TeamDashboard } from './TeamDashboard';
import type { QuestionGroupType } from '../../lib/question-group';
import type { GuideFileType } from '../../lib/guide';
import type { VideoFileType } from '../../lib/video';
import { cn } from '../../lib/classname';
type DashboardPageProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];

@ -11,8 +11,6 @@ import {
import { httpGet } from '../../lib/http';
import { ProgressNudge } from '../FrameRenderer/ProgressNudge';
import { getUrlParams } from '../../lib/browser.ts';
import { cn } from '../../lib/classname.ts';
import { getUser } from '../../lib/jwt.ts';
type EditorRoadmapProps = {
resourceId: string;

@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef } from 'react';
import './EditorRoadmapRenderer.css';
import { lazy, useCallback, useEffect, useRef } from 'react';
import {
renderResourceProgress,
updateResourceProgress,
@ -9,12 +10,17 @@ import {
} from '../../lib/resource-progress';
import { pageProgressMessage } from '../../stores/page';
import { useToast } from '../../hooks/use-toast';
import type { Edge, Node } from 'reactflow';
import { Renderer } from '../../../editor/renderer';
import type { Edge, Node } from '@roadmapsh/editor';
import { slugify } from '../../lib/slugger';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
const Renderer = lazy(() =>
import('@roadmapsh/editor').then((mod) => ({
default: mod.Renderer,
})),
);
export type RoadmapRendererProps = {
resourceId: string;
nodes: Node[];

@ -1,6 +1,6 @@
import '../GenerateRoadmap/GenerateRoadmap.css';
import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
import { renderFlowJSON } from '@roadmapsh/editor';
import { generateAIRoadmapFromText } from '@roadmapsh/editor';
import {
generateAICourseRoadmapStructure,
readAIRoadmapStream,

@ -1,3 +1,5 @@
import './GenerateRoadmap.css';
import {
type FormEvent,
type MouseEvent,
@ -6,10 +8,8 @@ import {
useRef,
useState,
} from 'react';
import './GenerateRoadmap.css';
import { useToast } from '../../hooks/use-toast';
import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { generateAIRoadmapFromText, renderFlowJSON } from '@roadmapsh/editor';
import { replaceChildren } from '../../lib/dom';
import {
isLoggedIn,
@ -278,6 +278,10 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) {
width: undefined,
height: undefined,
},
measured: {
width: undefined,
height: undefined,
},
})),
edges,
},

@ -1,7 +1,7 @@
import { ChevronDownIcon, StarIcon, User2Icon } from 'lucide-react';
import { useState } from 'react';
import { cn } from '../../../editor/utils/classname';
import { markdownToHtml } from '../../lib/markdown';
import { cn } from '../../lib/classname';
type Review = {
name: string;

@ -1,3 +1,4 @@
import '../FrameRenderer/FrameRenderer.css';
import {
useCallback,
useEffect,
@ -6,7 +7,6 @@ import {
useRef,
} from 'react';
import { Spinner } from '../ReactIcons/Spinner';
import '../FrameRenderer/FrameRenderer.css';
import type { TeamMember } from './TeamProgressPage';
import { httpGet } from '../../lib/http';
import {
@ -15,13 +15,12 @@ import {
type ResourceType,
updateResourceProgress,
} from '../../lib/resource-progress';
import CloseIcon from '../../icons/close.svg';
import { useToast } from '../../hooks/use-toast';
import { useAuth } from '../../hooks/use-auth';
import { pageProgressMessage } from '../../stores/page';
import type { GetRoadmapResponse } from '../CustomRoadmap/CustomRoadmap';
import { ReadonlyEditor } from '../../../editor/readonly-editor';
import type { Node } from 'reactflow';
import { ReadonlyEditor } from '@roadmapsh/editor';
import type { Node } from '@roadmapsh/editor';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { MemberProgressModalHeader } from './MemberProgressModalHeader';

@ -20,7 +20,7 @@ import { MemberProgressModalHeader } from './MemberProgressModalHeader';
import { replaceChildren } from '../../lib/dom.ts';
import { XIcon } from 'lucide-react';
import type { PageType } from '../CommandMenu/CommandMenu.tsx';
import { renderFlowJSON } from '../../../editor/renderer/renderer.ts';
import { renderFlowJSON } from '@roadmapsh/editor';
import { getResourceMeta } from '../../lib/roadmap.ts';
export type ProgressMapProps = {

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState, type RefObject } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { useKeydown } from '../../hooks/use-keydown';
import { httpGet } from '../../lib/http';
@ -7,7 +7,7 @@ import { topicSelectorAll } from '../../lib/resource-progress';
import { deleteUrlParam, getUrlParams } from '../../lib/browser';
import { useAuth } from '../../hooks/use-auth';
import type { GetRoadmapResponse } from '../CustomRoadmap/CustomRoadmap';
import { ReadonlyEditor } from '../../../editor/readonly-editor';
import { ReadonlyEditor } from '@roadmapsh/editor';
import { ModalLoader } from './ModalLoader.tsx';
import { UserProgressModalHeader } from './UserProgressModalHeader';
import { X } from 'lucide-react';
@ -173,7 +173,7 @@ export function UserCustomProgressModal(props: ProgressMapProps) {
variant="modal"
roadmap={roadmap!}
className="min-h-[400px]"
onRendered={(wrapperRef: RefObject<HTMLDivElement>) => {
onRendered={(wrapperRef) => {
const {
done = [],
learning = [],

@ -12,7 +12,7 @@ import { ModalLoader } from './ModalLoader.tsx';
import { UserProgressModalHeader } from './UserProgressModalHeader';
import { X } from 'lucide-react';
import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts';
import { renderFlowJSON } from '../../../editor/renderer/renderer.ts';
import { renderFlowJSON } from '@roadmapsh/editor';
export type ProgressMapProps = {
userId?: string;

@ -8,7 +8,7 @@ import {
import { useToast } from '../../hooks/use-toast';
import { replaceChildren } from '../../lib/dom.ts';
import type { GetUserProfileRoadmapResponse } from '../../api/user.ts';
import { ReadonlyEditor } from '../../../editor/readonly-editor.tsx';
import { ReadonlyEditor } from '@roadmapsh/editor';
import { cn } from '../../lib/classname.ts';
export type UserProfileRoadmapRendererProps = GetUserProfileRoadmapResponse & {
@ -96,7 +96,7 @@ export function UserProfileRoadmapRenderer(
edges,
}}
className="min-h-[1000px]"
onRendered={(wrapperRef: RefObject<HTMLDivElement>) => {
onRendered={(wrapperRef) => {
done?.forEach((topicId: string) => {
topicSelectorAll(topicId, wrapperRef?.current!).forEach(
(el) => {

@ -1,4 +1,6 @@
---
import '../styles/global.css';
import Analytics from '../components/Analytics/Analytics.astro';
import LoginPopup from '../components/AuthenticationFlow/LoginPopup.astro';
import Authenticator from '../components/Authenticator/Authenticator.astro';
@ -10,9 +12,9 @@ import { PageProgress } from '../components/PageProgress';
import { Toaster } from '../components/Toast';
import { PageSponsors } from '../components/PageSponsors/PageSponsors';
import { siteConfig } from '../lib/config';
import '../styles/global.css';
import { PageVisit } from '../components/PageVisit/PageVisit';
import type { ResourceType } from '../lib/resource-progress';
import ChangelogBanner from '../components/ChangelogBanner.astro';
import Clarity from '../components/Analytics/Clarity.astro';
import RedditPixel from '../components/Analytics/RedditPixel.astro';
import GoogleAd from '../components/Analytics/GoogleAd.astro';

@ -1,7 +1,28 @@
@import '@roadmapsh/editor/style.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
--color-gray-200: oklch(0.928 0.006 264.531);
border-color: var(--color-gray-200, currentColor);
}
}
@layer components {
.container {
@apply mx-auto !max-w-[830px] px-4;

Loading…
Cancel
Save