Skip to content

Commit

Permalink
Social Image Update: Absolute Positioning, Scale Calculation, and Pip…
Browse files Browse the repository at this point in the history
…eline Connectors (#278)

* fix: draw parentNodes in social image with absolute positioning, update calcScale, minor change in useLayout and edgeCreator

* fix: draw edges for social image with absolute positioning

* feat: draw connectors in open graph image

* fix: remove console.log

* fix: improve svg curves, test for calcScale added

* fix: sonarCloud bugs

* fix: tests added for drawEdges and drawConnectorEdges

* fix: sonarCloud duplicate error

* fix: minor refactor

* fix: sonarCloud issue

* fix: sonarCloud issue

* fix: sonarCloud issue

* fix: sonarCloud issue
  • Loading branch information
roshan-gh authored Jan 8, 2024
1 parent 7725f9a commit 44bd303
Show file tree
Hide file tree
Showing 11 changed files with 672 additions and 331 deletions.
11 changes: 10 additions & 1 deletion packages/otelbin/src/app/og/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

import React from "react";
import ConnectorIcon from "../../components/assets/svg/connector.svg";
import type { XYPosition } from "reactflow";

export interface IData {
label: string;
parentNode: string;
type: string;
id: string;
position: XYPosition;
}

const Node = ({ data, icon, type }: { data: IData; icon: React.ReactNode; type: string }) => {
Expand Down Expand Up @@ -41,7 +43,14 @@ const Node = ({ data, icon, type }: { data: IData; icon: React.ReactNode; type:
const isConnector = data.type.includes("connectors");

return (
<div tw={`h-[72px] w-[110px] flex-col items-center rounded-lg my-5 mx-[10px] flex`}>
<div
style={{
position: "absolute",
top: data.position.y,
left: data.position.x,
}}
tw={`h-20 w-[120px] flex-col items-center rounded-lg flex`}
>
<div
style={customNodeHeaderStyle}
tw={`px-3 text-xs font-medium h-[35%] w-full flex items-center justify-center text-white
Expand Down
4 changes: 2 additions & 2 deletions packages/otelbin/src/app/og/ParentNodeTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export default function ParentNodeTag({ tag }: { tag: string }) {
key={node.type}
style={{
position: "absolute",
top: -8,
left: -20,
top: 0,
left: 0,
display: "flex",
alignItems: "center",
backgroundColor: node.tagBackgroundColor,
Expand Down
163 changes: 67 additions & 96 deletions packages/otelbin/src/app/og/ParentsNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,37 @@
// SPDX-License-Identifier: Apache-2.0

import React from "react";
import { type Node } from "reactflow";
import type { Edge, Node } from "reactflow";
import ParentNodeTag from "./ParentNodeTag";
import ArrowRight from "../../components/assets/svg/move-right.svg";
import { ReceiversNode, ProcessorsNode, ExportersNode } from "./NodeTypes";
import { parentNodesConfig } from "~/components/react-flow/node-types/ParentsNode";
import { drawEdges } from "../s/[id]/metadataUtils";

const ParentsNode = ({ nodeData, nodes }: { nodeData: Node; nodes?: Node[] }) => {
const childNodes = nodes?.filter((node) => node.parentNode === nodeData.data.label);
const processorsNodesCount = childNodes?.filter((node) => node.type === "processorsNode").length ?? 0;
const nodesWidth = 110;
const sumOfExporterAndReceiver = 240;
const edgesWidth = 80;
const totalNodesWidth = (processorsNodesCount ?? 0) * nodesWidth + sumOfExporterAndReceiver;
const totalEdgeWidth = edgesWidth * (processorsNodesCount + 1);
const totalPaddingX = 40;
const maxWidth = totalNodesWidth + (processorsNodesCount + 2) * 20 + totalEdgeWidth + totalPaddingX;
export function svgArrowHead(id?: string) {
return (
<defs>
<marker
id={`arrowhead-${id}`}
viewBox="0 -5 10 10"
refX="5"
refY="0"
markerWidth="10"
markerHeight="10"
orient="auto"
fill="transparent"
stroke="#FFFFFF"
>
<g>
<path d="M0,-4L7,0L0,4" strokeWidth={0.5}></path>
<path d="M-1,-3.5L6,0L-1,3.5" strokeWidth={0.5}></path>
</g>
</marker>
</defs>
);
}

const ParentsNode = ({ nodeData, edges, nodes }: { nodeData: Node; edges: Edge[]; nodes?: Node[] }) => {
const maxWidth = nodeData.data.width;
const receivers = nodes
?.filter((node) => node.type === "receiversNode")
.filter((receiver) => receiver.parentNode === nodeData.data.label);
Expand All @@ -29,60 +43,15 @@ const ParentsNode = ({ nodeData, nodes }: { nodeData: Node; nodes?: Node[] }) =>
?.filter((node) => node.type === "processorsNode")
.filter((processor) => processor.parentNode === nodeData.data.label);

const nodeHeight = 72;
const nodeTotalMargin = 40;

function calcSVGHeight(nodes?: Node[]) {
const nodesCount = nodes?.length ?? 0;
return nodesCount * (nodeHeight + nodeTotalMargin) - nodeTotalMargin;
}
const parentNodeEdges = edges.filter((edge) => {
const sourceParent = edge.data.sourceParent;
const targetParent = edge.data.targetParent;
if (sourceParent === targetParent && sourceParent === nodeData.data.label) {
return edge;
}
});

function calcSVGPath(side: string, nodes?: Node[]) {
const nodesCount = nodes?.length ?? 0;
const height = nodesCount * (nodeHeight + nodeTotalMargin) - 40;

return (
<svg style={{ marginBottom: "30px" }} width="80" height={calcSVGHeight(nodes)} xmlns="http://www.w3.org/2000/svg">
<defs>
<marker
id="arrowhead"
viewBox="0 -5 10 10"
refX="5"
refY="0"
markerWidth="10"
markerHeight="10"
orient="auto"
fill="transparent"
stroke="#FFFFFF"
>
<g>
<path d="M0,-4L7,0L0,4" strokeWidth={0.5}></path>
<path d="M-1,-3.5L6,0L-1,3.5" strokeWidth={0.5}></path>
</g>
</marker>
</defs>
{Array.isArray(nodes) &&
nodes?.length > 0 &&
nodes.map((node, idx) => (
<path
key={node.id}
d={
side === "left"
? `M10 ${(idx + 1) * (height / nodesCount) - 75}
C 20,${(idx + 1) * (height / nodesCount) - 75},35,${height / 2 - 25}
50 ${height / 2 - 25}`
: `M10 ${height / 2 - 25}
C 20,${height / 2 - 25},35,${(idx + 1) * (height / nodesCount) - 75}
50 ${(idx + 1) * (height / nodesCount) - 75}`
}
stroke="#FFFFFF"
fill="transparent"
markerEnd="url(#arrowhead)"
/>
))}
</svg>
);
}
const edgesToDraw = drawEdges(parentNodeEdges, nodeData);

return (
<>
Expand All @@ -99,41 +68,43 @@ const ParentsNode = ({ nodeData, nodes }: { nodeData: Node; nodes?: Node[] }) =>
position: "relative",
backgroundColor: node.backgroundColor,
border: node.borderColor,
height: `${nodeData.data.height + 50}px`,
height: `${nodeData.data.height}px`,
width: maxWidth,
}}
tw="rounded-[4px] text-[10px] text-black my-3 px-5 py-2"
tw="rounded-[4px] text-[10px] text-black"
>
<ParentNodeTag tag={nodeData.data.label} />

<div style={{ display: "flex", justifyContent: "center" }}>
<div tw="flex items-center">
<div tw="flex flex-col justify-center">
{receivers?.map((receiver) => <ReceiversNode key={receiver.id} data={receiver.data} />)}
</div>
{processors?.length === 0 ? (
<></>
) : receivers?.length === 1 ? (
<ArrowRight />
) : (
calcSVGPath("left", receivers)
)}
</div>
<div tw="flex items-center">
<div tw="flex justify-center items-center">
{processors?.map((processor, idx) => (
<div key={processor.id} tw="flex justify-center items-center">
{idx > 0 ? <ArrowRight /> : <></>}
<ProcessorsNode data={processor.data} />
</div>
))}
</div>
{exporters?.length === 1 ? <ArrowRight /> : calcSVGPath("right", exporters)}
<div tw="flex flex-col justify-center">
{exporters?.map((exporter) => <ExportersNode key={exporter.id} data={exporter.data} />)}
</div>
</div>
</div>
{receivers?.map((receiver) => <ReceiversNode key={receiver.id} data={receiver.data} />)}
{processors?.map((processor) => <ProcessorsNode key={processor.id} data={processor.data} />)}
{exporters?.map((exporter) => <ExportersNode key={exporter.id} data={exporter.data} />)}
{Array.isArray(edgesToDraw) &&
edgesToDraw.length > 0 &&
edgesToDraw?.map((edge) => (
<svg
key={edge?.edge.id}
style={{ position: "absolute" }}
width={edge?.targetPosition.x}
height={
edge && edge?.targetPosition.y < edge?.sourcePosition.y
? edge?.sourcePosition.y
: edge?.targetPosition.y
}
xmlns="http://www.w3.org/2000/svg"
>
{svgArrowHead(edge?.edge.id)}
<path
key={edge?.edge.id}
d={`M${edge?.sourcePosition.x} ${edge?.sourcePosition.y} C ${
edge && edge?.sourcePosition.x + 30
} ${edge?.sourcePosition.y}, ${edge && edge?.targetPosition.x - 30} ${edge?.targetPosition
.y} ${edge?.targetPosition.x} ${edge?.targetPosition.y}
`}
stroke="#FFFFFF"
fill="transparent"
markerEnd={`url(#arrowhead-${edge?.edge.id})`}
/>
</svg>
))}
</div>
);
})}
Expand Down
70 changes: 58 additions & 12 deletions packages/otelbin/src/app/s/[id]/img/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@

import { ImageResponse, NextResponse, type NextRequest } from "next/server";
import { calcNodes } from "~/components/react-flow/useClientNodes";
import ParentsNode from "../../../og/ParentsNode";
import ParentsNode, { svgArrowHead } from "../../../og/ParentsNode";
import { Redis } from "@upstash/redis/nodejs";
import { getShortLinkPersistenceKey } from "~/lib/shortLink";
import type { IConfig } from "~/components/react-flow/dataType";
import { editorBinding } from "~/components/monaco-editor/editorBinding";
import JsYaml, { FAILSAFE_SCHEMA } from "js-yaml";
import { calcScale, toUrlState } from "../metadataUtils";
import { calcScale, drawConnectorEdges, toUrlState } from "../metadataUtils";
import Logo from "~/components/assets/svg/otelbin_logo_white.svg";
import { notFound } from "next/navigation";
import { calcEdges } from "~/components/react-flow/useEdgeCreator";
import { getLayoutedElements } from "~/components/react-flow/layout/useLayout";

export const runtime = "edge";

const redis = Redis.fromEnv();
const edgeWidth = 80;

export async function GET(request: NextRequest) {
const shortLinkId = request.nextUrl.searchParams.get("id") ?? "";
Expand All @@ -32,8 +33,19 @@ export async function GET(request: NextRequest) {
}
const { config } = toUrlState(url, [editorBinding]);
const jsonData = JsYaml.load(config, { schema: FAILSAFE_SCHEMA }) as IConfig;
const initNodes = calcNodes(jsonData, true);
const parentNodes = initNodes?.filter((node) => node.type === "parentNodeType");
const initNodes = calcNodes(jsonData);

const initEdges = calcEdges(initNodes ?? []);
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(initNodes ?? [], initEdges);
const parentNodes = layoutedNodes?.filter((node) => node.type === "parentNodeType");

const scale = calcScale(parentNodes)?.scale;
const totalYOffset = calcScale(parentNodes)?.totalYOffset;
const totalXOffset = calcScale(parentNodes)?.totalXOffset;

const connectorEdges = layoutedEdges?.filter((edge) => edge.data.type === "connector");

const connectorEdgesToDraw = drawConnectorEdges(connectorEdges ?? [], parentNodes, totalXOffset);

return new ImageResponse(
(
Expand All @@ -48,9 +60,7 @@ export async function GET(request: NextRequest) {
backgroundColor: "#151721",
position: "relative",
backgroundImage: `url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIiBmaWxsPSIjODI4MjhCIiBzdHJva2U9IiM4MjgyOEIiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1kb3QiPjxjaXJjbGUgY3g9IjEyLjEiIGN5PSIxMi4xIiByPSIwLjUiLz48L3N2Zz4=)`,
backgroundSize: `${Number(calcScale(edgeWidth, initNodes)) * 20}px ${
Number(calcScale(edgeWidth, initNodes)) * 20
}px`,
backgroundSize: `${Number(scale) * 20}px ${Number(scale) * 20}px`,
backgroundRepeat: "repeat",
backgroundPosition: "center",
}}
Expand All @@ -60,16 +70,52 @@ export async function GET(request: NextRequest) {
</div>
<div
style={{
transform: `scale(${calcScale(edgeWidth, initNodes)})`,
transformOrigin: "center left",
transform: `scale(${scale})`,
display: "flex",
flexDirection: "column",
justifyContent: "center",
position: "relative",
width: "100%",
}}
tw="bg-transparent"
>
{parentNodes?.map((parentNode) => (
<ParentsNode key={parentNode.id} nodeData={parentNode} nodes={initNodes} />
<div
key={parentNode.id}
style={{
display: "flex",
position: "absolute",
top: parentNode.position.y + totalYOffset,
left: parentNode.position.x + totalXOffset,
}}
>
<ParentsNode key={parentNode.id} nodeData={parentNode} nodes={layoutedNodes} edges={layoutedEdges} />
</div>
))}
{Array.isArray(connectorEdgesToDraw) &&
connectorEdgesToDraw.length > 0 &&
connectorEdgesToDraw?.map((edge) => (
<svg
key={edge.edge.id}
style={{ position: "absolute", top: totalYOffset }}
width={edge.targetPosition.x}
height={edge.targetPosition?.y < edge.sourcePosition.y ? edge.sourcePosition.y : edge.targetPosition.y}
xmlns="http://www.w3.org/2000/svg"
>
{svgArrowHead(edge?.edge.id)}
<path
key={edge.edge.id}
d={`M${edge.sourcePosition.x} ${edge.sourcePosition.y} C ${edge.sourcePosition.x + 40} ${
edge.sourcePosition.y
}, ${edge && edge.targetPosition?.x - 40} ${edge.targetPosition.y} ${edge.targetPosition.x} ${
edge.targetPosition.y
}
`}
stroke="#FFFFFF"
fill="transparent"
markerEnd={`url(#arrowhead-${edge.edge.id})`}
/>
</svg>
))}
</div>
</div>
),
Expand Down
Loading

0 comments on commit 44bd303

Please sign in to comment.