A web app to help you design things, local, offline, on device. In your browser.
TypeScript
53.1%
JSON
45.6%
CSS
0.9%
Markdown
0.3%
JavaScript
0.1%
'use client';
import React, { useRef } from 'react';
import { Group, Rect, Circle, Ellipse, RegularPolygon, Star, Arrow, Line } from 'react-konva';
import type Konva from 'konva';
import type { Layer, ShapeLayerData, GradientConfig } from '@/types';
interface ShapeLayerProps {
id: string;
layer: Layer;
isSelected: boolean;
onClick: (e: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => void;
onTransformEnd: (node: Konva.Node) => void;
onDragStart?: () => void;
onDragMove?: (node: Konva.Node) => void;
onDragEnd: (node: Konva.Node) => void;
}
export function ShapeLayer({
id,
layer,
isSelected,
onClick,
onTransformEnd,
onDragStart,
onDragMove,
onDragEnd,
}: ShapeLayerProps) {
const groupRef = useRef<Konva.Group>(null);
const data = layer.data as ShapeLayerData;
// Build fill props based on whether it's solid or gradient
const getFillProps = () => {
if (typeof data.fill === 'string') {
return { fill: data.fill };
}
const gradient = data.fill as GradientConfig;
if (gradient.type === 'linear') {
const angle = gradient.angle || 0;
const rad = (angle * Math.PI) / 180;
return {
fillLinearGradientStartPoint: {
x: layer.size.width / 2 - (Math.cos(rad) * layer.size.width) / 2,
y: layer.size.height / 2 - (Math.sin(rad) * layer.size.height) / 2,
},
fillLinearGradientEndPoint: {
x: layer.size.width / 2 + (Math.cos(rad) * layer.size.width) / 2,
y: layer.size.height / 2 + (Math.sin(rad) * layer.size.height) / 2,
},
fillLinearGradientColorStops: gradient.stops.flatMap((s) => [s.offset, s.color]),
};
}
return {
fillRadialGradientStartPoint: { x: layer.size.width / 2, y: layer.size.height / 2 },
fillRadialGradientEndPoint: { x: layer.size.width / 2, y: layer.size.height / 2 },
fillRadialGradientStartRadius: 0,
fillRadialGradientEndRadius: Math.max(layer.size.width, layer.size.height) / 2,
fillRadialGradientColorStops: gradient.stops.flatMap((s) => [s.offset, s.color]),
};
};
const commonProps = {
stroke: data.stroke,
strokeWidth: data.strokeWidth,
...getFillProps(),
};
const renderShape = () => {
switch (data.shapeType) {
case 'rectangle':
return (
<Rect
width={layer.size.width}
height={layer.size.height}
cornerRadius={data.cornerRadius || 0}
{...commonProps}
/>
);
case 'circle':
const radius = Math.min(layer.size.width, layer.size.height) / 2;
return (
<Circle
x={layer.size.width / 2}
y={layer.size.height / 2}
radius={radius}
{...commonProps}
/>
);
case 'ellipse':
return (
<Ellipse
x={layer.size.width / 2}
y={layer.size.height / 2}
radiusX={layer.size.width / 2}
radiusY={layer.size.height / 2}
{...commonProps}
/>
);
case 'polygon':
const polyRadius = Math.min(layer.size.width, layer.size.height) / 2;
return (
<RegularPolygon
x={layer.size.width / 2}
y={layer.size.height / 2}
sides={data.points || 6}
radius={polyRadius}
{...commonProps}
/>
);
case 'star':
const starRadius = Math.min(layer.size.width, layer.size.height) / 2;
return (
<Star
x={layer.size.width / 2}
y={layer.size.height / 2}
numPoints={data.points || 5}
innerRadius={starRadius * 0.5}
outerRadius={starRadius}
{...commonProps}
/>
);
case 'arrow':
return (
<Arrow
points={[0, layer.size.height / 2, layer.size.width, layer.size.height / 2]}
pointerLength={20}
pointerWidth={20}
{...commonProps}
/>
);
default:
return null;
}
};
return (
<Group
ref={groupRef}
id={id}
x={layer.position.x}
y={layer.position.y}
width={layer.size.width}
height={layer.size.height}
rotation={layer.rotation}
opacity={layer.opacity}
draggable={!layer.locked}
onClick={onClick}
onTap={onClick}
onDragStart={onDragStart}
onDragMove={(e) => onDragMove?.(e.target)}
onDragEnd={(e) => onDragEnd(e.target)}
onTransformEnd={(e) => onTransformEnd(e.target)}
>
{renderShape()}
</Group>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks