Skip to content

Commit

Permalink
feat: make sidebar width with scalible and handle drag and drop
Browse files Browse the repository at this point in the history
  • Loading branch information
egenerse committed Dec 31, 2024
1 parent e13ae33 commit 5ed8e47
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 117 deletions.
4 changes: 2 additions & 2 deletions library/lib/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { defaultEdges, defaultNodes } from "./initialElements"
import "@/styles/app.css"
import { Sidebar } from "@/components"
import { useCallback } from "react"
import { nodeTypes } from "./nodes"
import { diagramNodeTypes } from "./nodes"
import { useDragDrop } from "./hooks"

const defaultEdgeOptions: DefaultEdgeOptions = {
Expand All @@ -47,7 +47,7 @@ function App({ onReactFlowInit }: AppProps) {
<div style={{ display: "flex", width: "100vw", height: "100vh" }}>
<Sidebar />
<ReactFlow
nodeTypes={nodeTypes}
nodeTypes={diagramNodeTypes}
defaultEdgeOptions={defaultEdgeOptions}
nodes={nodes}
edges={edges}
Expand Down
18 changes: 18 additions & 0 deletions library/lib/components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react"

interface DividerProps {
style?: React.CSSProperties
}

export const Divider: React.FC<DividerProps> = ({ style }) => {
return (
<div
style={{
height: "2px",
width: "100%",
backgroundColor: "#000",
...style,
}}
/>
)
}
133 changes: 34 additions & 99 deletions library/lib/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,54 @@
import { DragEvent } from "react"
import { v4 as uuidv4 } from "uuid"
import { ClassType, DropNodeData } from "@/types"
import { ClassSVG, ColorDescriptionSVG, PackageSVG } from "@/svgs"

const SideBarElementWidth = 128
const SideBarElementHeight = 88
const SideBarElementScale = 0.8
import React, { DragEvent } from "react"
import { dropElementConfig, transformScale } from "@/constant"
import { Divider } from "./Divider"
import { DropNodeData } from "@/types"

const onDragStart = (event: DragEvent, { type, data }: DropNodeData) => {
event.dataTransfer.setData("text/plain", JSON.stringify({ type, data }))
event.dataTransfer.effectAllowed = "move"
}

// Common configuration for sidebar elements
const sideBarElements = [
{
name: "Class",
type: "class",
stereotype: undefined,
},
{
name: "Abstract",
type: "class",
stereotype: ClassType.Abstract,
},
{
name: "Enumeration",
type: "class",
stereotype: ClassType.Enumeration,
},
{
name: "Interface",
type: "class",
stereotype: ClassType.Interface,
},
]

export const Sidebar = () => {
return (
<aside style={{ height: "100%", backgroundColor: "#f0f0f0" }}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
gap: "8px",
margin: "10px",
}}
>
<div
onDragStart={(event: DragEvent) =>
onDragStart(event, {
type: "package",
data: {
name: "Package",
},
})
}
draggable
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
>
<PackageSVG
width={SideBarElementWidth / SideBarElementScale}
height={SideBarElementHeight / SideBarElementScale}
name="Package"
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
/>
</div>
{sideBarElements.map(({ name, type, stereotype }) => (
<div
key={name}
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
onDragStart={(event: DragEvent) =>
onDragStart(event, {
type,
data: {
name,
methods: [{ id: uuidv4(), name: "+ method()" }],
attributes: [{ id: uuidv4(), name: "+ attribute: Type" }],
stereotype,
},
})
}
draggable
>
<ClassSVG
width={SideBarElementWidth / SideBarElementScale}
height={SideBarElementHeight / SideBarElementScale}
methods={[{ id: uuidv4(), name: "+ method()" }]}
attributes={[{ id: uuidv4(), name: "+ attribute: Type" }]}
name={name}
stereotype={stereotype}
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
/>
</div>
{dropElementConfig.map((config) => (
<React.Fragment key={`${config.type}_${config.name}`}>
{/* Add separator before the Color Description */}
{config.type === "colorDescription" && (
<Divider style={{ margin: "3px 0" }} />
)}
<div
style={{
width: config.width * transformScale,
height: config.height * transformScale,
overflow: "hidden",
zIndex: 2,
}}
draggable
onDragStart={(event: DragEvent) =>
onDragStart(event, {
type: config.type,
data: config.defaultData,
})
}
>
{React.createElement(config.svg, {
width: config.width,
height: config.height,
...config.defaultData,
transformScale,
})}
</div>
</React.Fragment>
))}
<div style={{ height: 2, width: "auto", backgroundColor: "black" }} />
<div
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
onDragStart={(event: DragEvent) =>
onDragStart(event, {
type: "colorDescription",
data: {
description: "Color Description",
},
})
}
draggable
>
<ColorDescriptionSVG
width={SideBarElementWidth / SideBarElementScale}
height={48 / SideBarElementScale}
description="Color Description"
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
/>
</div>
</div>
</aside>
)
Expand Down
1 change: 1 addition & 0 deletions library/lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./Text"
export * from "./ThemedElements"
export * from "./Sidebar"
export * from "./Divider"
86 changes: 86 additions & 0 deletions library/lib/constant/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ClassSVG, PackageSVG, ColorDescriptionSVG } from "@/svgs"
import { generateUUID } from "@/utils"
import { ClassType } from "@/types"
import { DiagramNodeTypeKeys } from "@/nodes"

export const transformScale = 0.8
const droppedElementWidth = 160
const droppedElementHeight = 110

export const dropElementConfig: {
type: DiagramNodeTypeKeys
name: string
width: number
height: number
defaultData: Record<string, unknown>
svg: React.FC<any>
}[] = [
{
type: "package",
name: "Package",
width: droppedElementWidth,
height: droppedElementHeight,
defaultData: { name: "Package" },
svg: (props) => <PackageSVG {...props} />,
},
{
type: "class",
name: "Class",
width: droppedElementWidth,
height: droppedElementHeight,
defaultData: {
name: "Class",
methods: [{ id: generateUUID(), name: "+ method()" }],
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
},
svg: (props) => <ClassSVG {...props} />,
},
{
type: "class",
name: "Abstract",
width: droppedElementWidth,
height: droppedElementHeight,
defaultData: {
name: "Abstract",
stereotype: ClassType.Abstract,
methods: [{ id: generateUUID(), name: "+ method()" }],
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
},
svg: (props) => <ClassSVG {...props} />,
},
{
type: "class",
name: "Enumeration",
width: droppedElementWidth,
height: droppedElementHeight,
defaultData: {
name: "Enumeration",
stereotype: ClassType.Enumeration,
methods: [{ id: generateUUID(), name: "+ method()" }],
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
},
svg: (props) => <ClassSVG {...props} />,
},
{
type: "class",
name: "Interface",
width: droppedElementWidth,
height: droppedElementHeight,
defaultData: {
name: "Interface",
stereotype: ClassType.Interface,
methods: [{ id: generateUUID(), name: "+ method()" }],
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
},
svg: (props) => <ClassSVG {...props} />,
},
{
type: "colorDescription",
name: "Color Description",
width: droppedElementWidth,
height: 48,
defaultData: { description: "Color Description" },
svg: (props) => <ColorDescriptionSVG {...props} />,
},
]
22 changes: 14 additions & 8 deletions library/lib/hooks/useDragDrop.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { dropElementConfig } from "@/constant"
import { DropNodeData } from "@/types"
import { generateUUID } from "@/utils"
import { useReactFlow, type Node } from "@xyflow/react"
Expand All @@ -9,31 +10,36 @@ export const useDragDrop = () => {
const onDrop = useCallback(
(event: DragEvent) => {
event.preventDefault()
const data = JSON.parse(
const dropData = JSON.parse(
event.dataTransfer.getData("text/plain")
) as DropNodeData

// check if the dropped element is valid
if (!data.type) {
const config = dropElementConfig.find(
(config) => config.type === dropData.type
)
// Validate the dropped element type
if (!config) {
console.warn(`Unknown drop element type: ${dropData.type}`)
return
}

const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
})

const newNode: Node = {
width: 200,
height: 110,
width: config.width,
height: config.height,
id: generateUUID(),
type: data.type,
type: dropData.type,
position,
data: data.data,
data: { ...config.defaultData, ...dropData.data },
}

setNodes((nds) => nds.concat(newNode))
},
[screenToFlowPosition]
[screenToFlowPosition, setNodes]
)

const onDragOver = useCallback((event: DragEvent) => {
Expand Down
8 changes: 5 additions & 3 deletions library/lib/nodes/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { NodeTypes } from "@xyflow/react"
import { NodeTypes } from "@xyflow/react" // Explicitly differentiate imported type
import { Class, ColorDescription, Package } from "./classDiagram"

export const nodeTypes: NodeTypes = {
export const diagramNodeTypes = {
package: Package,
class: Class,
colorDescription: ColorDescription,
}
} satisfies NodeTypes

export type DiagramNodeTypeKeys = keyof typeof diagramNodeTypes
9 changes: 8 additions & 1 deletion library/lib/svgs/classDiagram/ClassSVG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ClassSVGProps = {
attributes: ExtraElements[]
stereotype?: ClassType
name: string
transformScale?: number
svgAttributes?: SVGAttributes<SVGElement>
}

Expand All @@ -20,6 +21,7 @@ export function ClassSVG({
attributes,
stereotype,
name,
transformScale,
svgAttributes,
}: ClassSVGProps) {
const headerHeight = 50
Expand All @@ -32,7 +34,12 @@ export function ClassSVG({
<svg
width={width}
height={height}
style={{ transformOrigin: "0 0" }}
z={2}
style={{
transformOrigin: "0 0",
transformBox: "content-box",
transform: transformScale ? `scale(${transformScale})` : undefined,
}}
{...svgAttributes}
>
<g>
Expand Down
Loading

0 comments on commit 5ed8e47

Please sign in to comment.