Skip to content

Commit

Permalink
position snippets, adjust styling
Browse files Browse the repository at this point in the history
  • Loading branch information
pokornyd committed Feb 21, 2025
1 parent ebfea89 commit 2b271e4
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 99 deletions.
9 changes: 4 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const App: React.FC = () => {

const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
const [environmentId, setEnvironmentId] = useState<string>("");
const [showSnippets, setShowSnippets] = useState(false);

const handleNodeSelect = useCallback((nodeId: string) => {
setSelectedNodeId(nodeId);
Expand Down Expand Up @@ -89,13 +90,11 @@ const App: React.FC = () => {
<ReactFlowProvider>
<SnippetsProvider
snippets={snippets}
showSnippets={true}
toggleSnippets={function(): void {
throw new Error("Function not implemented.");
}}
showSnippets={showSnippets}
toggleSnippets={() => setShowSnippets(!showSnippets)}
>
<div className="w-64 border-r border-gray-200 relative z-10 shadow-lg shadow-neutral-300">
<Sidebar types={contentTypes} snippets={snippets} onTypeSelect={handleNodeSelect} />
<Sidebar types={contentTypes} snippets={snippets} onMenuSelect={handleNodeSelect} />
</div>
<div className="z-1">
<Toolbar environmentId={environmentId} />
Expand Down
57 changes: 48 additions & 9 deletions src/components/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState, useEffect, useCallback, useMemo } from "react";
import ReactFlow, { MiniMap, Controls, Background, Node, NodeChange, applyNodeChanges } from "reactflow";
import ReactFlow, { MiniMap, Controls, Background, Node, NodeChange, applyNodeChanges, Edge } from "reactflow";
import "reactflow/dist/style.css";
import { getLayoutedElements, isRelationshipElement } from "../utils/layout";
import { ContentTypeNode } from "./ContentTypeNode";
import { ContentTypeElements, ContentTypeModels, ContentTypeSnippetModels } from "@kontent-ai/management-sdk";
import { useExpandedNodes } from "../contexts/ExpandedNodesContext";
import { SnippetNode } from "./SnippetNode";
import { useSnippets } from "../contexts/SnippetsContext";

type ContentType = ContentTypeModels.ContentType;

Expand Down Expand Up @@ -34,11 +35,11 @@ type ProcessedGraph = {
};

const processSnippets = (snippets: ContentTypeSnippetModels.ContentTypeSnippet[]): Array<ProcessedNode> => {
return snippets.map((snippet, index) => ({
return snippets.map((snippet) => ({
id: snippet.id,
type: "snippet",
data: { id: snippet.id, label: snippet.name, elements: snippet.elements },
position: { x: 0, y: index * 100 }, // Position snippets vertically
position: { x: 0, y: 0 }, // layouting is done separately
}));
};

Expand Down Expand Up @@ -111,7 +112,29 @@ export const Canvas: React.FC<CanvasProps> = ({
const processedSnippets = useMemo(() => processSnippets(snippets), [snippets]);
const processedGraph = useMemo(() => processContentTypes(types), [types]);

// Create edges for snippet relationships
const snippetEdges = useMemo(() => {
const edges: Edge[] = [];
processedSnippets.forEach(snippet => {
snippet.data.elements.forEach(element => {
if (isRelationshipElement(element)) {
element.allowed_content_types?.forEach(allowed => {
edges.push({
id: `${snippet.id}-${element.id}-${allowed.id}`,
source: snippet.id,
sourceHandle: `source-${element.id}`,
target: allowed.id ?? "",
targetHandle: "target",
});
});
}
});
});
return edges;
}, [processedSnippets]);

const { expandedNodes } = useExpandedNodes();
const { showSnippets } = useSnippets();

const updateNodeState = useCallback(
(nodes: Node[]): Node[] =>
Expand All @@ -128,16 +151,32 @@ export const Canvas: React.FC<CanvasProps> = ({

// Initialize nodes with layout applied.
const [nodes, setNodes] = useState<Node[]>(() => {
const initialNodes = updateNodeState([...processedSnippets, ...processedGraph.nodes]);
const initialNodes = updateNodeState(processedGraph.nodes);
return getLayoutedElements(initialNodes, processedGraph.edges).nodes;
});

useEffect(() => {
setNodes((prevNodes) => {
const updatedNodes = updateNodeState(prevNodes);
return getLayoutedElements(updatedNodes, processedGraph.edges).nodes;
const baseNodes = prevNodes.filter(node => node.type !== "snippet");
const updatedNodes = updateNodeState(baseNodes);
const layoutedNodes = getLayoutedElements(updatedNodes, processedGraph.edges).nodes;

if (showSnippets) {
const snippetNodes = updateNodeState(processedSnippets);
const layoutedSnippets = getLayoutedElements(snippetNodes, [], "LR").nodes
.map(node => ({
...node,
position: {
x: node.position.x - 500,
y: node.position.y,
},
}));
return [...layoutedNodes, ...layoutedSnippets];
}

return layoutedNodes;
});
}, [expandedNodes, selectedNodeId, updateNodeState, processedGraph.edges]);
}, [expandedNodes, selectedNodeId, updateNodeState, processedGraph.edges, showSnippets, processedSnippets]);

const onNodesChange = useCallback((changes: NodeChange[]) => {
setNodes((nds) => applyNodeChanges(changes, nds));
Expand All @@ -146,8 +185,8 @@ export const Canvas: React.FC<CanvasProps> = ({
return (
<div className="w-full h-full">
<ReactFlow
nodes={nodes}
edges={processedGraph.edges}
nodes={showSnippets ? [...processedSnippets, ...nodes] : nodes}
edges={showSnippets ? [...processedGraph.edges, ...snippetEdges] : processedGraph.edges}
onNodesChange={onNodesChange}
nodeTypes={nodeTypes}
onNodeClick={(_, node) => onNodeSelect(node.id)}
Expand Down
34 changes: 7 additions & 27 deletions src/components/ContentTypeNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,17 @@ import React from "react";
import { SourceHandle, TargetHandle } from "./Handles";
import { NodeProps, useReactFlow } from "reactflow";
import { useExpandedNodes } from "../contexts/ExpandedNodesContext";
import { ContentTypeNodeData, getFilteredElementsData, isRelationshipElement, isNodeRelated } from "../utils/layout";
import {
ContentTypeNodeData,
getFilteredElementsData,
isRelationshipElement,
isNodeRelated,
elementTypeMap,
} from "../utils/layout";
import { ActionButton } from "./ActionButton";
import { ContentTypeElements } from "@kontent-ai/management-sdk";
import { useSnippets } from "../contexts/SnippetsContext";

type ElementType = ContentTypeElements.ContentTypeElementModel["type"];

type ElementTypeLabels = {
[K in ElementType]: string;
};

const elementTypeLabels: ElementTypeLabels = {
text: "Text",
rich_text: "Rich Text",
number: "Number",
multiple_choice: "Multiple Choice",
date_time: "Date & Time",
asset: "Asset",
modular_content: "Linked Items",
subpages: "Subpages",
url_slug: "URL Slug",
guidelines: "Guidelines",
taxonomy: "Taxonomy",
custom: "Custom",
snippet: "Content Type Snippet",
};

export const ContentTypeNode: React.FC<NodeProps<ContentTypeNodeData>> = ({
data,
selected,
Expand All @@ -41,10 +25,6 @@ export const ContentTypeNode: React.FC<NodeProps<ContentTypeNodeData>> = ({
const expanded = expandedNodes.has(data.id);
const { filteredElements } = getFilteredElementsData(data);

const elementTypeMap: ReadonlyMap<ElementType, string> = new Map(
Object.entries(elementTypeLabels) as [ElementType, string][],
);

const containerStyle: React.CSSProperties = {
paddingTop: 5,
paddingBottom: 5,
Expand Down
11 changes: 6 additions & 5 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ type Snippet = ContentTypeSnippetModels.ContentTypeSnippet;
interface SidebarProps {
types: ContentType[];
snippets: Snippet[];
onTypeSelect: (typeId: string) => void;
onMenuSelect: (typeId: string) => void;
}

export const Sidebar: React.FC<SidebarProps> = ({ types, snippets, onTypeSelect }) => {
export const Sidebar: React.FC<SidebarProps> = ({ types, snippets, onMenuSelect }) => {
const { toggleNode } = useExpandedNodes();
const { getNodes, setCenter } = useReactFlow();

const handleTypeClick = (typeId: string) => {
onTypeSelect(typeId);
const handleSidebarSelection = (typeId: string) => {
onMenuSelect(typeId);
toggleNode(typeId, true);

const node = getNodes().find(n => n.id === typeId);
Expand All @@ -37,7 +37,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ types, snippets, onTypeSelect
{types.map(({ id, name }) => (
<li
key={id}
onClick={() => handleTypeClick(id)}
onClick={() => handleSidebarSelection(id)}
className="py-2 pl-6 w-full text-sm cursor-pointer hover:bg-gradient-to-r hover:from-[#5b4ff5]/2 hover:via-[#5b4ff5]/4 hover:to-[#5b4ff5]/6"
>
{name}
Expand All @@ -49,6 +49,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ types, snippets, onTypeSelect
{snippets.map(({ id, name }) => (
<li
key={id}
onClick={() => handleSidebarSelection(id)}
className="py-2 pl-6 w-full text-sm cursor-pointer hover:bg-gradient-to-r hover:from-[#5b4ff5]/2 hover:via-[#5b4ff5]/4 hover:to-[#5b4ff5]/6"
>
{name}
Expand Down
32 changes: 9 additions & 23 deletions src/components/SnippetNode.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from "react";
import { NodeProps, Position, Handle, useReactFlow } from "reactflow";
import { NodeProps, useReactFlow } from "reactflow";
import { useExpandedNodes } from "../contexts/ExpandedNodesContext";
import { isRelationshipElement, isNodeRelated, SnippetNodeData } from "../utils/layout";
import { isRelationshipElement, isNodeRelated, SnippetNodeData, elementTypeMap } from "../utils/layout";
import { ActionButton } from "./ActionButton";
import { SourceHandle } from "./Handles";

export const SnippetNode: React.FC<NodeProps<SnippetNodeData>> = ({
data,
Expand All @@ -19,7 +20,7 @@ export const SnippetNode: React.FC<NodeProps<SnippetNodeData>> = ({
borderRadius: 16,
background: selected ? "#f3f3fe" : "white",
cursor: "pointer",
minWidth: 250,
minWidth: 350,
position: "relative",
};

Expand Down Expand Up @@ -53,33 +54,24 @@ export const SnippetNode: React.FC<NodeProps<SnippetNodeData>> = ({
icon="🔍"
/>
</div>
<Handle
type="source"
position={Position.Right}
id="source"
className="custom-handle right"
/>
<div style={{ display: "flex", flexDirection: "column" }}>
{data.elements.map((el, i) =>
el.type !== "guidelines" && el.type !== "snippet" && (
<div
key={el.id}
className="flex items-center justify-between py-1 px-2"
className="flex items-center justify-between py-1 px-2 relative"
style={{
borderBottom: i < data.elements.length - 1 ? "1px solid #ddd" : "none",
}}
>
<div className="font-bold text-xs">{el.name}</div>
<div className="text-xs">
{el.type}
{elementTypeMap.get(el.type) || el.type}
</div>
{isRelationshipElement(el) && (
<Handle
type="source"
position={Position.Right}
id={`source-${el.id}`}
className="custom-handle right"
/>
<div className="absolute right-0 top-1/2 -translate-y-1/2">
<SourceHandle id={`source-${el.id}`} />
</div>
)}
</div>
)
Expand All @@ -100,12 +92,6 @@ export const SnippetNode: React.FC<NodeProps<SnippetNodeData>> = ({
title="Isolate related nodes"
icon="🔍"
/>
<Handle
type="source"
position={Position.Right}
id="source"
className="custom-handle right"
/>
</div>
)}
</div>
Expand Down
25 changes: 14 additions & 11 deletions src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { useExpandedNodes } from "../contexts/ExpandedNodesContext";
import { useReactFlow } from "reactflow";
import html2canvas from "html2canvas-pro";
import jsPDF from "jspdf";
import { useSnippets } from "../contexts/SnippetsContext";

export const Toolbar: React.FC<{ environmentId: string }> = ({ environmentId }) => {
const { expandedNodes, toggleNode } = useExpandedNodes();
const { getNodes, fitView, setNodes } = useReactFlow();
const { showSnippets, toggleSnippets } = useSnippets();
const [isExporting, setIsExporting] = useState(false);

const handleExpandCollapse = () => {
Expand All @@ -28,8 +30,9 @@ export const Toolbar: React.FC<{ environmentId: string }> = ({ environmentId })
}))
);

expandedNodes.forEach(node => {
toggleNode(node, false);
const allNodes = getNodes();
allNodes.forEach(node => {
toggleNode(node.id, false);
});
setTimeout(() => fitView({ duration: 800 }), 50);
};
Expand Down Expand Up @@ -90,19 +93,19 @@ export const Toolbar: React.FC<{ environmentId: string }> = ({ environmentId })
>
Reset View
</div>
{
/* <label className="switch mx-2">

<label className="switch ml-5 mr-2">
<input
type="checkbox"
id="switch"
checked={showAllToolbars}
onChange={(e) => setShowAllToolbars(e.target.checked)}
checked={showSnippets}
onChange={() => {
toggleSnippets();
setTimeout(() => fitView({ duration: 800 }), 50);
}}
/>
<span className="slider purple"></span>
<span className="text-sm mx-2">Show all node toolbars</span>
</label> */
}
<span className="text-sm ml-2">Show snippets</span>
</label>

<div className="flex-1" />

Expand Down
Loading

0 comments on commit 2b271e4

Please sign in to comment.