import type { ComponentState } from "../../hooks/useComponent";
import { useDragComponent } from "../../hooks/useDragComponent";
import { ComponentType } from "../../types/component";
import type { ButtonGeneralData, Component, TableGeneralData, TextFieldGeneralData, TextGeneralData, UIData } from "../../types/component";
import { useEffect, useRef, useState } from "react";
interface CanvasComponentProps {
component: Component,
width: number,
height: number
}
function CanvasComponent({ component, width, height }: CanvasComponentProps) {
const baseStyle = {
width: width,
height: height
}
switch (component.type) {
case ComponentType.Button:
return (
<button
style={baseStyle}
className="bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors border border-blue-600 shadow-sm"
>
{(component.generalData as ButtonGeneralData).text}
</button>
)
case ComponentType.Text:
return (
<div
key={component.id}
style={baseStyle}
className="text-gray-900 flex items-center overflow-hidden"
>
{(component.generalData as TextGeneralData).text}
</div>
)
case ComponentType.TextField:
return (
<input
key={component.id}
type={(component.generalData as TextFieldGeneralData).text}
placeholder={'テキストを入力'}
style={baseStyle}
readOnly
className="border border-gray-500 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
/>
)
case ComponentType.Table:
return (
<div className="flex-col w-full overflow-hidden border" style={baseStyle}>
<div className="flex w-full">
{(component.generalData as TableGeneralData).columns.map((col, idx) =>
<div key={`tableCol${idx}`} className={`flex h-8 text-sm flex-1 justify-center item-center border ${idx !== 0 && "border-l-0"} overflow-hidden py-1 px-2`}>
{col}
</div>
)}
</div>
<div className="flex w-full">
{(component.generalData as TableGeneralData).columns.map((_, idx) =>
<div key={`tableCol${idx}`} className={`flex flex-1 h-8 justify-center item-center border border-t-0 ${idx !== 0 && "border-l-0"}`}>
</div>
)}
</div>
<div className="flex w-full">
{(component.generalData as TableGeneralData).columns.map((_, idx) =>
<div key={`tableCol${idx}`} className={`flex flex-1 h-8 justify-center item-center border border-t-0 ${idx !== 0 && "border-l-0"}`}>
</div>
)}
</div>
<div className="flex w-full">
{(component.generalData as TableGeneralData).columns.map((_, idx) =>
<div key={`tableCol${idx}`} className={`flex flex-1 h-8 justify-center item-center border border-t-0 ${idx !== 0 && "border-l-0"}`}>
</div>
)}
</div>
</div>
)
}
}
interface CanvasProps {
componentState: ComponentState
canvasWidth: number
moveComponent: (index: number, uiData: UIData) => void
selectComponent: (index: number) => void
resetSelectingComponent: () => void
}
export default function Canvas({
componentState,
canvasWidth,
moveComponent,
selectComponent: selectComponentGrobal,
resetSelectingComponent
}: CanvasProps) {
const outRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLDivElement>(null);
const [widgetScale] = useState(0.4);
const [scale, setScale] = useState(1);
const {
componentX,
componentY,
componentW,
componentH,
onDragStart,
draggingComponent,
draggingSquare,
onDragEnd
} = useDragComponent(
componentState.selectingComponent,
componentState.selectingComponentIndex,
moveComponent,
scale,
widgetScale
);
const borderWidth = 1;
const padding = 4;
useEffect(() => {
if (!outRef.current) return;
const observer = new ResizeObserver((entries) => {
const width = entries[0].contentRect.width;
const newScale = width / canvasWidth;
setScale(newScale);
})
observer.observe(outRef.current);
return () => observer.disconnect();
}, [canvasWidth]);
const componentStyle = (isSelecting: boolean, component: Component) => {
if (isSelecting) return {
position: "absolute" as const,
left: `${componentX - padding * 2 - borderWidth}px`,
top: `${componentY - padding * 2 - borderWidth}px`,
width: `${componentW + padding * 4 + borderWidth * 2}px`,
height: `${componentH + padding * 4 + borderWidth * 2}px`,
}
return {
position: "absolute" as const,
left: `${component.uiData.posX}px`,
top: `${component.uiData.posY}px`,
width: `${component.uiData.width}px`,
height: `${component.uiData.height}px`,
}
}
const squareStyle = (index: number) => {
const squareWidth = padding * 2 + borderWidth;
if (index == 0) {
return {
position: "absolute" as const,
left: "0px",
top: "0px",
width: `${squareWidth}px`,
height: `${squareWidth}px`,
}
} else if (index == 1) {
return {
position: "absolute" as const,
left: `${squareWidth + componentW}px`,
top: "0px",
width: `${squareWidth}px`,
height: `${squareWidth}px`,
}
} else if (index == 2) {
return {
position: "absolute" as const,
left: `${squareWidth + componentW}px`,
top: `${squareWidth + componentH}px`,
width: `${squareWidth}px`,
height: `${squareWidth}px`,
}
} else {
return {
position: "absolute" as const,
left: "0px",
top: `${squareWidth + componentH}px`,
width: `${squareWidth}px`,
height: `${squareWidth}px`,
}
}
}
const borderStyle = (index: number) => {
const squareWidth = padding * 2 + borderWidth;
if (index == 0) {
return {
position: "absolute" as const,
left: `${squareWidth}px`,
top: `${padding}px`,
width: `${componentW}px`,
height: `${borderWidth}px`,
}
} else if (index == 1) {
return {
position: "absolute" as const,
left: `${squareWidth + componentW + padding}px`,
top: `${squareWidth}px`,
width: `${borderWidth}px`,
height: `${componentH}px`,
}
} else if (index == 2) {
return {
position: "absolute" as const,
left: `${squareWidth}px`,
top: `${squareWidth + componentH + padding}px`,
width: `${componentW}px`,
height: `${borderWidth}px`,
}
} else {
return {
position: "absolute" as const,
left: `${padding}px`,
top: `${squareWidth}px`,
width: `${borderWidth}px`,
height: `${componentH}px`,
}
}
}
const selectComopnent = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
e.stopPropagation();
selectComponentGrobal(index);
}
const resetSelectComponent = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e.stopPropagation();
resetSelectingComponent();
}
return (
<div className="flex items-center justify-center h-full bg-gray-50"
ref={outRef}
>
<div
className="bg-white border border-gray-200 shadow-lg rounded-lg relative overflow-hidden"
ref={canvasRef}
onClick={resetSelectComponent}
style={{
aspectRatio: '16/9',
width: `${canvasWidth}px`,
transform: `scale(${scale})`
}}
>
{/* Canvas Content */}
<div className="w-full h-full relative" style={{ transform: `scale(${widgetScale})`, transformOrigin: "top left" }}>
{componentState.curScreenComponents.map((component, index) =>
<div
key={index}
style={componentStyle(componentState.selectingComponentIndex === index, component)}
onClick={(e) => { selectComopnent(e, index) }}
draggable
onDragStart={(e) => { if (componentState.selectingComponentIndex === index) onDragStart(e) }}
onDrag={(e) => { if (componentState.selectingComponentIndex === index) draggingComponent(e) }}
onDragEnd={(e) => { if (componentState.selectingComponentIndex === index) onDragEnd(e) }}
>
{componentState.selectingComponentIndex === index ?
<div>
<div
style={{
position: "absolute",
left: `${padding * 2 + borderWidth}px`,
top: `${padding * 2 + borderWidth}px`
}}
>
<CanvasComponent component={component} width={componentW} height={componentH} />
</div>
{[0, 1, 2, 3].map((num) => (
<div
key={`square${num}`}
style={squareStyle(num)}
draggable
onDragStart={onDragStart}
onDrag={(e) => { draggingSquare(e, num) }}
onDragEnd={onDragEnd}
className="bg-black"
/>
))}
{[0, 1, 2, 3].map((num) => (
<div
key={`border${num}`}
style={borderStyle(num)}
className="bg-black"
/>
))}
</div> :
<div>
<CanvasComponent component={component} width={component.uiData.width} height={component.uiData.height} />
</div>
}
</div>
)}
</div>
{/* Grid overlay (optional, for development) */}
<div
className="absolute inset-0 pointer-events-none opacity-5"
style={{
backgroundImage: `
linear-gradient(to right, #000 1px, transparent 1px),
linear-gradient(to bottom, #000 1px, transparent 1px)
`,
backgroundSize: '20px 20px'
}}
/>
</div>
</div>
);
}