-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ccc9186
commit 12dc8b4
Showing
5 changed files
with
538 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import MdxPage from './MdxPage'; | ||
import dynamic from 'next/dynamic'; | ||
|
||
export default function CanvasElement({ data }) { | ||
// Normalize nodes | ||
const normalizeNodePositions = (nodes) => { | ||
const offsetX = Math.abs(Math.min(...nodes.map((node) => node.x), 0)); | ||
const offsetY = Math.abs(Math.min(...nodes.map((node) => node.y), 0)); | ||
|
||
return nodes.map((node) => ({ | ||
...node, | ||
x: (node.x + offsetX), | ||
y: (node.y + offsetY), | ||
})); | ||
}; | ||
|
||
// Function to render the different types of nodes based on the node type | ||
const renderNodeContent = (node) => { | ||
switch (node.type) { | ||
case 'text': | ||
return ( | ||
<div style={{ padding: '1rem' }}> | ||
<MdxPage source={node.source} frontMatter={{}} /> | ||
</div> | ||
); | ||
case 'file': | ||
if (node.file.endsWith('.md') || node.file.endsWith('.mdx')) { | ||
return ( | ||
<div style={{ padding: '1rem' }}> | ||
<MdxPage source={node.source} frontMatter={{}} /> | ||
</div> | ||
); | ||
} | ||
if (node.file.endsWith('.pdf')) { | ||
const PdfView = dynamic(import('@/components/PdfView'), { | ||
ssr: false, | ||
}); | ||
return ( | ||
<div style={{ padding: '.2rem' }}> | ||
<PdfView | ||
filePath={node.file} | ||
width={node.width} | ||
height={node.height} | ||
/> | ||
</div> | ||
); | ||
} | ||
return ( | ||
<div> | ||
<img src={node.file} alt={`File: ${node.file}`} /> | ||
</div> | ||
); | ||
case 'link': | ||
const [_, id] = node.url.split('='); | ||
return ( | ||
<div> | ||
<iframe | ||
width={`${node.width}`} | ||
height={`${node.height}`} | ||
src={`https://www.youtube.com/embed/${id}?si=Jbz-2kTWpwH21cTD`} | ||
title='YouTube video player' | ||
allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share' | ||
allowFullScreen | ||
style={{ padding: '.2rem' }} | ||
></iframe> | ||
</div> | ||
); | ||
|
||
default: | ||
return null; | ||
} | ||
}; | ||
|
||
const getEdgeCoordinates = (node, side) => { | ||
const position = { x: node.x, y: node.y }; | ||
switch (side) { | ||
case 'top': | ||
position.x += node.width / 2; | ||
break; | ||
case 'bottom': | ||
position.x += node.width / 2; | ||
position.y += node.height; | ||
break; | ||
case 'left': | ||
position.y += node.height / 2; | ||
break; | ||
case 'right': | ||
position.x += node.width; | ||
position.y += node.height / 2; | ||
break; | ||
default: | ||
// Handle unexpected side value if necessary | ||
break; | ||
} | ||
return position; | ||
}; | ||
|
||
const normalizedNodes = normalizeNodePositions(data.nodes); | ||
|
||
const renderNodes = normalizedNodes.map((node, index) => { | ||
return ( | ||
<> | ||
{node.file && <div style={{ | ||
position: 'absolute', | ||
left: `${node.x}px`, | ||
top: `${node.y - 24}px`, | ||
whiteSpace: "nowrap" | ||
}} | ||
>{node.file}</div>} | ||
<div | ||
key={index} | ||
style={{ | ||
position: 'absolute', | ||
left: `${node.x}px`, | ||
top: `${node.y}px`, | ||
width: `${node.width}px`, | ||
height: `${node.height}px`, | ||
borderRadius: '12px', | ||
}} | ||
className='border border-black dark:border-white' | ||
> | ||
{renderNodeContent(node)} | ||
</div> | ||
</> | ||
); | ||
}); | ||
|
||
const renderEdges = data.edges.map((edge) => { | ||
const fromNode = normalizedNodes.find((node) => node.id === edge.fromNode); | ||
const toNode = normalizedNodes.find((node) => node.id === edge.toNode); | ||
|
||
if (!fromNode || !toNode) { | ||
console.warn(`Nodes for edge ${edge.id} not found.`); | ||
return null; | ||
} | ||
|
||
const fromPosition = getEdgeCoordinates(fromNode, edge.fromSide); | ||
const toPosition = getEdgeCoordinates(toNode, edge.toSide); | ||
|
||
const arrowheadMarkerId = `arrowhead-${edge.id}`; | ||
|
||
return ( | ||
<svg key={edge.id} style={{ position: 'absolute', overflow: 'visible' }}> | ||
<defs> | ||
<marker | ||
id={arrowheadMarkerId} | ||
markerWidth='10' | ||
markerHeight='7' | ||
refX='8' | ||
refY='3.5' | ||
orient='auto' | ||
markerUnits='strokeWidth' | ||
> | ||
<circle cx="5" cy="3.5" r="3" className='fill-black dark:fill-white' /> | ||
</marker> | ||
</defs> | ||
<path | ||
d={`M ${fromPosition.x} ${fromPosition.y} C ${(fromPosition.x + toPosition.x) / 2} ${fromPosition.y}, ${(fromPosition.x + toPosition.x) / 2} ${toPosition.y}, ${toPosition.x} ${toPosition.y}`} | ||
className='stroke-black dark:stroke-white' | ||
strokeWidth='1.4' | ||
fill='none' | ||
markerEnd={`url(#${arrowheadMarkerId})`} | ||
/> | ||
{edge.label && ( | ||
<text | ||
x={(fromPosition.x + toPosition.x) / 2} | ||
y={(fromPosition.y + toPosition.y) / 2} | ||
style={{ userSelect: 'none' }} | ||
> | ||
{edge.label} | ||
</text> | ||
)} | ||
</svg> | ||
); | ||
}); | ||
|
||
return ( | ||
<div style={{ height: '3000px' }}> | ||
<div style={{ position: 'relative' }}> | ||
{renderEdges} | ||
{renderNodes} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { useState } from 'react'; | ||
import { Document, Page } from 'react-pdf'; | ||
import 'react-pdf/dist/Page/AnnotationLayer.css'; | ||
import 'react-pdf/dist/Page/TextLayer.css'; | ||
|
||
import { pdfjs } from 'react-pdf'; | ||
|
||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`; | ||
|
||
export default function PdfView({ | ||
filePath, | ||
width, | ||
height, | ||
}: { | ||
filePath: string; | ||
width: number; | ||
height: number; | ||
}) { | ||
const [numPages, setNumPages] = useState<number>(); | ||
const [pageNumber, setPageNumber] = useState<number>(1); | ||
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { | ||
setNumPages(numPages); | ||
} | ||
|
||
const handleNextPage = () => { | ||
if (pageNumber < numPages) { | ||
setPageNumber(pageNumber + 1); | ||
} | ||
}; | ||
|
||
const handlePrevPage = () => { | ||
if (pageNumber > 1) { | ||
setPageNumber(pageNumber - 1); | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Document file={filePath} onLoadSuccess={onDocumentLoadSuccess}> | ||
<Page pageNumber={pageNumber} width={width} height={height} /> | ||
</Document> | ||
<div className="flex items-center mt-4"> | ||
<button | ||
className="bg-blue-500 text-white py-2 px-4 rounded mr-2 transition duration-300 ease-in-out hover:bg-blue-600 cursor-pointer" | ||
onClick={handlePrevPage} | ||
disabled={pageNumber <= 1} | ||
> | ||
Previous | ||
</button> | ||
<p className="text-lg"> | ||
Page {pageNumber} of {numPages} | ||
</p> | ||
<button | ||
className="bg-blue-500 text-white py-2 px-4 rounded ml-2 transition duration-300 ease-in-out hover:bg-blue-600 cursor-pointer" | ||
onClick={handleNextPage} | ||
disabled={pageNumber >= numPages} | ||
> | ||
Next | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.