feat: add background panel for solid and gradient backgrounds

feat: implement code inspector with language detection and theme selection

feat: create text inspector for text properties and styling

style: add global styles and custom scrollbar for better UI

chore: initialize main entry point for the application

feat: set up Zustand store for canvas state management

feat: define types for canvas elements and background options

feat: implement code highlighting utility with language detection

chore: configure TypeScript settings for the project

chore: set up Vite configuration for React and Tailwind CSS
This commit is contained in:
2026-01-07 16:07:30 +02:00
commit bb5a9e0715
30 changed files with 6844 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
import React, { useRef, useEffect, useState } from 'react';
import { Group, Rect, Text, Transformer } from 'react-konva';
import type Konva from 'konva';
import type { TextElement } from '../../types';
interface TextBlockProps {
element: TextElement;
isSelected: boolean;
onSelect: () => void;
onChange: (updates: Partial<TextElement>) => void;
}
const TextBlock: React.FC<TextBlockProps> = ({ element, isSelected, onSelect, onChange }) => {
const groupRef = useRef<Konva.Group>(null);
const textRef = useRef<Konva.Text>(null);
const trRef = useRef<Konva.Transformer>(null);
const [textDimensions, setTextDimensions] = useState({ width: 200, height: 30 });
const { x, y, rotation, props } = element;
useEffect(() => {
if (isSelected && trRef.current && groupRef.current) {
trRef.current.nodes([groupRef.current]);
trRef.current.getLayer()?.batchDraw();
}
}, [isSelected]);
useEffect(() => {
if (textRef.current) {
setTextDimensions({
width: textRef.current.width(),
height: textRef.current.height(),
});
}
}, [props.text, props.fontSize, props.fontFamily, props.bold, props.italic]);
const handleDragEnd = (e: Konva.KonvaEventObject<DragEvent>) => {
onChange({
x: e.target.x(),
y: e.target.y(),
});
};
const handleTransformEnd = () => {
const node = groupRef.current;
if (!node) return;
node.scaleX(1);
node.scaleY(1);
onChange({
x: node.x(),
y: node.y(),
rotation: node.rotation(),
});
};
const totalWidth = textDimensions.width + props.padding * 2;
const totalHeight = textDimensions.height + props.padding * 2;
const fontStyle = [
props.bold ? 'bold' : '',
props.italic ? 'italic' : '',
].filter(Boolean).join(' ') || 'normal';
return (
<>
<Group
ref={groupRef}
x={x}
y={y}
rotation={rotation}
draggable={!element.locked}
onClick={onSelect}
onTap={onSelect}
onDragEnd={handleDragEnd}
onTransformEnd={handleTransformEnd}
>
{/* Background */}
{props.background && (
<Rect
x={-props.padding}
y={-props.padding}
width={totalWidth}
height={totalHeight}
fill={props.background.color}
cornerRadius={props.cornerRadius}
/>
)}
{/* Text */}
<Text
ref={textRef}
text={props.text}
fontSize={props.fontSize}
fontFamily={props.fontFamily}
fontStyle={fontStyle}
fill={props.color}
align={props.align}
textDecoration={props.underline ? 'underline' : ''}
/>
</Group>
{isSelected && (
<Transformer
ref={trRef}
flipEnabled={false}
enabledAnchors={['middle-left', 'middle-right']}
boundBoxFunc={(oldBox, newBox) => {
if (Math.abs(newBox.width) < 50) {
return oldBox;
}
return newBox;
}}
/>
)}
</>
);
};
export default TextBlock;