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 } from 'react-konva';
import Konva from 'konva';
import type {
Layer,
EffectLayerData,
GlassEffectConfig,
BlurEffectConfig,
GlowEffectConfig,
ShadowEffectConfig,
} from '@/types';
interface EffectLayerProps {
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 EffectLayer({
id,
layer,
isSelected,
onClick,
onTransformEnd,
onDragStart,
onDragMove,
onDragEnd,
}: EffectLayerProps) {
const groupRef = useRef<Konva.Group>(null);
const data = layer.data as EffectLayerData;
const renderEffect = () => {
switch (data.effectType) {
case 'glass':
return <GlassEffect layer={layer} config={data.config as GlassEffectConfig} />;
case 'blur':
return <BlurEffect layer={layer} config={data.config as BlurEffectConfig} />;
case 'glow':
return <GlowEffect layer={layer} config={data.config as GlowEffectConfig} />;
case 'shadow':
return <ShadowEffect layer={layer} config={data.config as ShadowEffectConfig} />;
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)}
>
{renderEffect()}
</Group>
);
}
// Glass Effect Component
function GlassEffect({ layer, config }: { layer: Layer; config: GlassEffectConfig }) {
// Konva doesn't support backdrop-filter, so we simulate the glass effect
// with semi-transparent overlays and gradients
return (
<Group>
{/* Base glass panel */}
<Rect
width={layer.size.width}
height={layer.size.height}
fill={`rgba(255, 255, 255, ${config.opacity})`}
cornerRadius={config.borderRadius}
stroke={config.borderColor}
strokeWidth={config.borderWidth}
shadowColor="rgba(0, 0, 0, 0.1)"
shadowBlur={20}
shadowOffsetY={10}
/>
{/* Inner glow / highlight */}
<Rect
x={config.borderWidth}
y={config.borderWidth}
width={layer.size.width - config.borderWidth * 2}
height={layer.size.height - config.borderWidth * 2}
fillLinearGradientStartPoint={{ x: 0, y: 0 }}
fillLinearGradientEndPoint={{ x: 0, y: layer.size.height * 0.3 }}
fillLinearGradientColorStops={[
0,
'rgba(255, 255, 255, 0.2)',
1,
'rgba(255, 255, 255, 0)',
]}
cornerRadius={config.borderRadius - config.borderWidth}
listening={false}
/>
{/* Subtle border highlight */}
<Rect
x={1}
y={1}
width={layer.size.width - 2}
height={layer.size.height - 2}
stroke="rgba(255, 255, 255, 0.1)"
strokeWidth={1}
cornerRadius={config.borderRadius - 1}
listening={false}
/>
</Group>
);
}
// Blur Effect Component (Spotlight/Focus area)
function BlurEffect({ layer, config }: { layer: Layer; config: BlurEffectConfig }) {
if (config.shape === '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}
fill="rgba(0, 0, 0, 0.5)"
filters={[Konva.Filters.Blur]}
blurRadius={config.blur}
/>
);
}
return (
<Rect
width={layer.size.width}
height={layer.size.height}
fill="rgba(0, 0, 0, 0.5)"
filters={[Konva.Filters.Blur]}
blurRadius={config.blur}
/>
);
}
// Glow Effect Component
function GlowEffect({ layer, config }: { layer: Layer; config: GlowEffectConfig }) {
const centerX = layer.size.width / 2;
const centerY = layer.size.height / 2;
const maxRadius = Math.max(layer.size.width, layer.size.height) / 2;
return (
<Group>
{/* Outer glow */}
<Circle
x={centerX}
y={centerY}
radius={maxRadius}
fillRadialGradientStartPoint={{ x: 0, y: 0 }}
fillRadialGradientEndPoint={{ x: 0, y: 0 }}
fillRadialGradientStartRadius={0}
fillRadialGradientEndRadius={maxRadius}
fillRadialGradientColorStops={[
0,
config.color,
0.5,
config.color.replace(')', ', 0.5)').replace('rgb', 'rgba'),
1,
'transparent',
]}
shadowColor={config.color}
shadowBlur={config.blur}
shadowOffset={{ x: 0, y: 0 }}
/>
</Group>
);
}
// Shadow Effect Component
function ShadowEffect({ layer, config }: { layer: Layer; config: ShadowEffectConfig }) {
return (
<Rect
width={layer.size.width}
height={layer.size.height}
fill="transparent"
shadowColor={config.color}
shadowBlur={config.blur}
shadowOffsetX={config.offsetX}
shadowOffsetY={config.offsetY}
/>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks