feat: Add editor UI toggle functionality
- 캔버스 편집 UI 표시/숨김 기능을 추가했습니다. - 에디터 툴바에 토글 버튼을 추가하여 UI 표시 상태를 제어할 수 있습니다. - 에디터 UI가 숨겨졌을 때 캔버스에 마우스 이벤트가 전달되지 않도록 수정했습니다.
This commit is contained in:
parent
0c3c0b606e
commit
fed9dc7606
6
.idea/AICommit.xml
generated
Normal file
6
.idea/AICommit.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.github.blarc.ai.commits.intellij.plugin.settings.ProjectSettings">
|
||||||
|
<option name="splitButtonActionSelectedLLMClientId" value="2f900cba-1f90-431b-acb2-e4e6ac66b31e" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
30
dist/index.css
vendored
30
dist/index.css
vendored
@ -11,6 +11,36 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
.editor-toolbar {
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.editor-toggle-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #383838;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: 2px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.editor-toggle-btn:hover {
|
||||||
|
background: #404040;
|
||||||
|
border-color: #00aaff;
|
||||||
|
}
|
||||||
|
.editor-toggle-btn.active {
|
||||||
|
background: #2d5a7a;
|
||||||
|
border-color: #00aaff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
.editor-main {
|
.editor-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|||||||
2
dist/index.css.map
vendored
2
dist/index.css.map
vendored
File diff suppressed because one or more lines are too long
49
dist/index.js
vendored
49
dist/index.js
vendored
@ -655,7 +655,8 @@ var EditorCanvas = ({
|
|||||||
draggingPointIndex,
|
draggingPointIndex,
|
||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging,
|
onStopDragging,
|
||||||
style: customStyle
|
style: customStyle,
|
||||||
|
showEditor = true
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = (0, import_react4.useRef)(null);
|
const containerRef = (0, import_react4.useRef)(null);
|
||||||
const [canvasSize, setCanvasSize] = (0, import_react4.useState)({ width: 0, height: 0 });
|
const [canvasSize, setCanvasSize] = (0, import_react4.useState)({ width: 0, height: 0 });
|
||||||
@ -704,7 +705,7 @@ var EditorCanvas = ({
|
|||||||
);
|
);
|
||||||
const handleCanvasMouseDown = (0, import_react4.useCallback)(
|
const handleCanvasMouseDown = (0, import_react4.useCallback)(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
const y = (e.clientY - rect.top) / rect.height;
|
const y = (e.clientY - rect.top) / rect.height;
|
||||||
@ -715,11 +716,11 @@ var EditorCanvas = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedArea, isPointInPolygon]
|
[showEditor, selectedArea, isPointInPolygon]
|
||||||
);
|
);
|
||||||
const handleMouseMove = (0, import_react4.useCallback)(
|
const handleMouseMove = (0, import_react4.useCallback)(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
const y = (e.clientY - rect.top) / rect.height;
|
const y = (e.clientY - rect.top) / rect.height;
|
||||||
@ -738,7 +739,7 @@ var EditorCanvas = ({
|
|||||||
setDragStartPos({ x, y });
|
setDragStartPos({ x, y });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
||||||
);
|
);
|
||||||
const handleMouseUp = (0, import_react4.useCallback)(() => {
|
const handleMouseUp = (0, import_react4.useCallback)(() => {
|
||||||
if (draggingPointIndex !== null) {
|
if (draggingPointIndex !== null) {
|
||||||
@ -829,12 +830,19 @@ var EditorCanvas = ({
|
|||||||
{
|
{
|
||||||
ref: containerRef,
|
ref: containerRef,
|
||||||
className: "editor-canvas",
|
className: "editor-canvas",
|
||||||
style: { width, height, position: "relative", cursor: getCursorStyle() },
|
style: {
|
||||||
onMouseDown: handleCanvasMouseDown,
|
width,
|
||||||
onMouseMove: handleMouseMove,
|
height,
|
||||||
|
position: "relative",
|
||||||
|
cursor: showEditor ? getCursorStyle() : "default",
|
||||||
|
pointerEvents: showEditor ? "auto" : "none"
|
||||||
|
// 에디터 숨김 시 포인터 이벤트 비활성화
|
||||||
|
},
|
||||||
|
onMouseDown: showEditor ? handleCanvasMouseDown : void 0,
|
||||||
|
onMouseMove: showEditor ? handleMouseMove : void 0,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }),
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
showEditor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -863,7 +871,7 @@ var EditorCanvas = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && canvasSize.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
"canvas",
|
"canvas",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -887,7 +895,7 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && selectedArea.basePoints.map((point, index) => {
|
showEditor && selectedArea && selectedArea.basePoints.map((point, index) => {
|
||||||
const handleStyle = editorStyle.pointHandle || {};
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
"div",
|
"div",
|
||||||
@ -1149,6 +1157,7 @@ var DistortionEditor = ({
|
|||||||
stopDragging,
|
stopDragging,
|
||||||
getSelectedArea
|
getSelectedArea
|
||||||
} = useDistortionEditor(initialAreas);
|
} = useDistortionEditor(initialAreas);
|
||||||
|
const [showEditor, setShowEditor] = (0, import_react5.useState)(true);
|
||||||
(0, import_react5.useEffect)(() => {
|
(0, import_react5.useEffect)(() => {
|
||||||
onAreasChange?.(state.areas);
|
onAreasChange?.(state.areas);
|
||||||
}, [state.areas, onAreasChange]);
|
}, [state.areas, onAreasChange]);
|
||||||
@ -1182,7 +1191,17 @@ var DistortionEditor = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const selectedArea = getSelectedArea();
|
const selectedArea = getSelectedArea();
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "distortion-editor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-main", children: [
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "distortion-editor", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "editor-toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
className: `editor-toggle-btn ${showEditor ? "active" : ""}`,
|
||||||
|
onClick: () => setShowEditor(!showEditor),
|
||||||
|
title: showEditor ? "\uC5D0\uB514\uD130 \uC228\uAE30\uAE30 (\uC65C\uACE1 \uD6A8\uACFC\uB9CC \uBCF4\uAE30)" : "\uC5D0\uB514\uD130 \uD45C\uC2DC",
|
||||||
|
children: showEditor ? "\u{1F441}\uFE0F \uC5D0\uB514\uD130 \uC228\uAE30\uAE30" : "\u270F\uFE0F \uC5D0\uB514\uD130 \uD45C\uC2DC"
|
||||||
|
}
|
||||||
|
) }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-main", children: [
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "editor-canvas-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "editor-canvas-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
||||||
EditorCanvas,
|
EditorCanvas,
|
||||||
{
|
{
|
||||||
@ -1196,7 +1215,8 @@ var DistortionEditor = ({
|
|||||||
draggingPointIndex: state.draggingPointIndex,
|
draggingPointIndex: state.draggingPointIndex,
|
||||||
onStartDragging: startDragging,
|
onStartDragging: startDragging,
|
||||||
onStopDragging: stopDragging,
|
onStopDragging: stopDragging,
|
||||||
style: canvasStyle
|
style: canvasStyle,
|
||||||
|
showEditor
|
||||||
}
|
}
|
||||||
) }),
|
) }),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [
|
||||||
@ -1212,7 +1232,8 @@ var DistortionEditor = ({
|
|||||||
),
|
),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
||||||
] })
|
] })
|
||||||
] }) });
|
] })
|
||||||
|
] });
|
||||||
};
|
};
|
||||||
// Annotate the CommonJS export names for ESM import in node:
|
// Annotate the CommonJS export names for ESM import in node:
|
||||||
0 && (module.exports = {
|
0 && (module.exports = {
|
||||||
|
|||||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
51
dist/index.mjs
vendored
51
dist/index.mjs
vendored
@ -459,7 +459,7 @@ var ImageDistortion = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/DistortionEditor.tsx
|
// src/editor/DistortionEditor.tsx
|
||||||
import { useEffect as useEffect4 } from "react";
|
import { useEffect as useEffect4, useState as useState4 } from "react";
|
||||||
|
|
||||||
// src/editor/hooks/useDistortionEditor.ts
|
// src/editor/hooks/useDistortionEditor.ts
|
||||||
import { useState as useState2, useCallback as useCallback2 } from "react";
|
import { useState as useState2, useCallback as useCallback2 } from "react";
|
||||||
@ -609,7 +609,8 @@ var EditorCanvas = ({
|
|||||||
draggingPointIndex,
|
draggingPointIndex,
|
||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging,
|
onStopDragging,
|
||||||
style: customStyle
|
style: customStyle,
|
||||||
|
showEditor = true
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef3(null);
|
const containerRef = useRef3(null);
|
||||||
const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 });
|
const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 });
|
||||||
@ -658,7 +659,7 @@ var EditorCanvas = ({
|
|||||||
);
|
);
|
||||||
const handleCanvasMouseDown = useCallback3(
|
const handleCanvasMouseDown = useCallback3(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
const y = (e.clientY - rect.top) / rect.height;
|
const y = (e.clientY - rect.top) / rect.height;
|
||||||
@ -669,11 +670,11 @@ var EditorCanvas = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedArea, isPointInPolygon]
|
[showEditor, selectedArea, isPointInPolygon]
|
||||||
);
|
);
|
||||||
const handleMouseMove = useCallback3(
|
const handleMouseMove = useCallback3(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
const y = (e.clientY - rect.top) / rect.height;
|
const y = (e.clientY - rect.top) / rect.height;
|
||||||
@ -692,7 +693,7 @@ var EditorCanvas = ({
|
|||||||
setDragStartPos({ x, y });
|
setDragStartPos({ x, y });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
||||||
);
|
);
|
||||||
const handleMouseUp = useCallback3(() => {
|
const handleMouseUp = useCallback3(() => {
|
||||||
if (draggingPointIndex !== null) {
|
if (draggingPointIndex !== null) {
|
||||||
@ -783,12 +784,19 @@ var EditorCanvas = ({
|
|||||||
{
|
{
|
||||||
ref: containerRef,
|
ref: containerRef,
|
||||||
className: "editor-canvas",
|
className: "editor-canvas",
|
||||||
style: { width, height, position: "relative", cursor: getCursorStyle() },
|
style: {
|
||||||
onMouseDown: handleCanvasMouseDown,
|
width,
|
||||||
onMouseMove: handleMouseMove,
|
height,
|
||||||
|
position: "relative",
|
||||||
|
cursor: showEditor ? getCursorStyle() : "default",
|
||||||
|
pointerEvents: showEditor ? "auto" : "none"
|
||||||
|
// 에디터 숨김 시 포인터 이벤트 비활성화
|
||||||
|
},
|
||||||
|
onMouseDown: showEditor ? handleCanvasMouseDown : void 0,
|
||||||
|
onMouseMove: showEditor ? handleMouseMove : void 0,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }),
|
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }),
|
||||||
/* @__PURE__ */ jsx2(
|
showEditor && /* @__PURE__ */ jsx2(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -817,7 +825,7 @@ var EditorCanvas = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && canvasSize.width > 0 && /* @__PURE__ */ jsx2(
|
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ jsx2(
|
||||||
"canvas",
|
"canvas",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -841,7 +849,7 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
selectedArea && selectedArea.basePoints.map((point, index) => {
|
showEditor && selectedArea && selectedArea.basePoints.map((point, index) => {
|
||||||
const handleStyle = editorStyle.pointHandle || {};
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
return /* @__PURE__ */ jsx2(
|
return /* @__PURE__ */ jsx2(
|
||||||
"div",
|
"div",
|
||||||
@ -1103,6 +1111,7 @@ var DistortionEditor = ({
|
|||||||
stopDragging,
|
stopDragging,
|
||||||
getSelectedArea
|
getSelectedArea
|
||||||
} = useDistortionEditor(initialAreas);
|
} = useDistortionEditor(initialAreas);
|
||||||
|
const [showEditor, setShowEditor] = useState4(true);
|
||||||
useEffect4(() => {
|
useEffect4(() => {
|
||||||
onAreasChange?.(state.areas);
|
onAreasChange?.(state.areas);
|
||||||
}, [state.areas, onAreasChange]);
|
}, [state.areas, onAreasChange]);
|
||||||
@ -1136,7 +1145,17 @@ var DistortionEditor = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const selectedArea = getSelectedArea();
|
const selectedArea = getSelectedArea();
|
||||||
return /* @__PURE__ */ jsx5("div", { className: "distortion-editor", children: /* @__PURE__ */ jsxs4("div", { className: "editor-main", children: [
|
return /* @__PURE__ */ jsxs4("div", { className: "distortion-editor", children: [
|
||||||
|
/* @__PURE__ */ jsx5("div", { className: "editor-toolbar", children: /* @__PURE__ */ jsx5(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
className: `editor-toggle-btn ${showEditor ? "active" : ""}`,
|
||||||
|
onClick: () => setShowEditor(!showEditor),
|
||||||
|
title: showEditor ? "\uC5D0\uB514\uD130 \uC228\uAE30\uAE30 (\uC65C\uACE1 \uD6A8\uACFC\uB9CC \uBCF4\uAE30)" : "\uC5D0\uB514\uD130 \uD45C\uC2DC",
|
||||||
|
children: showEditor ? "\u{1F441}\uFE0F \uC5D0\uB514\uD130 \uC228\uAE30\uAE30" : "\u270F\uFE0F \uC5D0\uB514\uD130 \uD45C\uC2DC"
|
||||||
|
}
|
||||||
|
) }),
|
||||||
|
/* @__PURE__ */ jsxs4("div", { className: "editor-main", children: [
|
||||||
/* @__PURE__ */ jsx5("div", { className: "editor-canvas-container", children: /* @__PURE__ */ jsx5(
|
/* @__PURE__ */ jsx5("div", { className: "editor-canvas-container", children: /* @__PURE__ */ jsx5(
|
||||||
EditorCanvas,
|
EditorCanvas,
|
||||||
{
|
{
|
||||||
@ -1150,7 +1169,8 @@ var DistortionEditor = ({
|
|||||||
draggingPointIndex: state.draggingPointIndex,
|
draggingPointIndex: state.draggingPointIndex,
|
||||||
onStartDragging: startDragging,
|
onStartDragging: startDragging,
|
||||||
onStopDragging: stopDragging,
|
onStopDragging: stopDragging,
|
||||||
style: canvasStyle
|
style: canvasStyle,
|
||||||
|
showEditor
|
||||||
}
|
}
|
||||||
) }),
|
) }),
|
||||||
/* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [
|
/* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [
|
||||||
@ -1166,7 +1186,8 @@ var DistortionEditor = ({
|
|||||||
),
|
),
|
||||||
/* @__PURE__ */ jsx5(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
/* @__PURE__ */ jsx5(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
||||||
] })
|
] })
|
||||||
] }) });
|
] })
|
||||||
|
] });
|
||||||
};
|
};
|
||||||
export {
|
export {
|
||||||
ANIMATION_CONFIG,
|
ANIMATION_CONFIG,
|
||||||
|
|||||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { DistortionArea } from '../types/area';
|
import { DistortionArea } from '../types/area';
|
||||||
import { DistortionEditorProps } from './types';
|
import { DistortionEditorProps } from './types';
|
||||||
import { useDistortionEditor } from './hooks/useDistortionEditor';
|
import { useDistortionEditor } from './hooks/useDistortionEditor';
|
||||||
@ -28,6 +28,9 @@ export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|||||||
getSelectedArea,
|
getSelectedArea,
|
||||||
} = useDistortionEditor(initialAreas);
|
} = useDistortionEditor(initialAreas);
|
||||||
|
|
||||||
|
// 에디터 모드 토글 상태
|
||||||
|
const [showEditor, setShowEditor] = useState(true);
|
||||||
|
|
||||||
// 영역 변경 시 콜백 호출
|
// 영역 변경 시 콜백 호출
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onAreasChange?.(state.areas);
|
onAreasChange?.(state.areas);
|
||||||
@ -72,6 +75,17 @@ export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="distortion-editor">
|
<div className="distortion-editor">
|
||||||
|
{/* 에디터 모드 토글 버튼 */}
|
||||||
|
<div className="editor-toolbar">
|
||||||
|
<button
|
||||||
|
className={`editor-toggle-btn ${showEditor ? 'active' : ''}`}
|
||||||
|
onClick={() => setShowEditor(!showEditor)}
|
||||||
|
title={showEditor ? '에디터 숨기기 (왜곡 효과만 보기)' : '에디터 표시'}
|
||||||
|
>
|
||||||
|
{showEditor ? '👁️ 에디터 숨기기' : '✏️ 에디터 표시'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="editor-main">
|
<div className="editor-main">
|
||||||
{/* 왼쪽: 캔버스 */}
|
{/* 왼쪽: 캔버스 */}
|
||||||
<div className="editor-canvas-container">
|
<div className="editor-canvas-container">
|
||||||
@ -87,6 +101,7 @@ export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|||||||
onStartDragging={startDragging}
|
onStartDragging={startDragging}
|
||||||
onStopDragging={stopDragging}
|
onStopDragging={stopDragging}
|
||||||
style={canvasStyle}
|
style={canvasStyle}
|
||||||
|
showEditor={showEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,8 @@ interface EditorCanvasProps {
|
|||||||
onStopDragging: () => void;
|
onStopDragging: () => void;
|
||||||
/** 에디터 캔버스 스타일 커스터마이징 */
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
style?: EditorCanvasStyle;
|
style?: EditorCanvasStyle;
|
||||||
|
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||||
|
showEditor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||||
@ -31,6 +33,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
onStartDragging,
|
onStartDragging,
|
||||||
onStopDragging,
|
onStopDragging,
|
||||||
style: customStyle,
|
style: customStyle,
|
||||||
|
showEditor = true,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
|
const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
|
||||||
@ -93,7 +96,8 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
// 캔버스 클릭 (사각형 내부 클릭 감지)
|
// 캔버스 클릭 (사각형 내부 클릭 감지)
|
||||||
const handleCanvasMouseDown = useCallback(
|
const handleCanvasMouseDown = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
// 에디터가 숨겨진 상태면 동작하지 않음
|
||||||
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
|
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
@ -107,12 +111,13 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedArea, isPointInPolygon]
|
[showEditor, selectedArea, isPointInPolygon]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseMove = useCallback(
|
const handleMouseMove = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
if (!selectedArea || !containerRef.current) return;
|
// 에디터가 숨겨진 상태면 동작하지 않음
|
||||||
|
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||||
|
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
@ -139,7 +144,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
setDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트
|
setDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseUp = useCallback(() => {
|
const handleMouseUp = useCallback(() => {
|
||||||
@ -270,14 +275,21 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="editor-canvas"
|
className="editor-canvas"
|
||||||
style={{width, height, position: 'relative', cursor: getCursorStyle()}}
|
style={{
|
||||||
onMouseDown={handleCanvasMouseDown}
|
width,
|
||||||
onMouseMove={handleMouseMove}
|
height,
|
||||||
|
position: 'relative',
|
||||||
|
cursor: showEditor ? getCursorStyle() : 'default',
|
||||||
|
pointerEvents: showEditor ? 'auto' : 'none', // 에디터 숨김 시 포인터 이벤트 비활성화
|
||||||
|
}}
|
||||||
|
onMouseDown={showEditor ? handleCanvasMouseDown : undefined}
|
||||||
|
onMouseMove={showEditor ? handleMouseMove : undefined}
|
||||||
>
|
>
|
||||||
{/* ImageDistortion 컴포넌트 */}
|
{/* ImageDistortion 컴포넌트 */}
|
||||||
<ImageDistortion imageSrc={imageSrc} areas={areas}/>
|
<ImageDistortion imageSrc={imageSrc} areas={areas}/>
|
||||||
|
|
||||||
{/* 오버레이 SVG */}
|
{/* 오버레이 SVG - 에디터 모드일 때만 표시 */}
|
||||||
|
{showEditor && (
|
||||||
<svg
|
<svg
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -310,9 +322,10 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) */}
|
{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) - 에디터 모드일 때만 표시 */}
|
||||||
{selectedArea && canvasSize.width > 0 && (
|
{showEditor && selectedArea && canvasSize.width > 0 && (
|
||||||
<canvas
|
<canvas
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -336,8 +349,8 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 선택된 영역의 포인트 핸들 */}
|
{/* 선택된 영역의 포인트 핸들 - 에디터 모드일 때만 표시 */}
|
||||||
{selectedArea &&
|
{showEditor && selectedArea &&
|
||||||
selectedArea.basePoints.map((point, index) => {
|
selectedArea.basePoints.map((point, index) => {
|
||||||
const handleStyle = editorStyle.pointHandle || {};
|
const handleStyle = editorStyle.pointHandle || {};
|
||||||
return (
|
return (
|
||||||
|
|||||||
59
src/editor/constants.ts
Normal file
59
src/editor/constants.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { EditorCanvasStyle } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기본 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
export const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle = {
|
||||||
|
// 3단계 원 스타일 (외부 -> 내부)
|
||||||
|
circleLevels: [
|
||||||
|
{
|
||||||
|
radius: 0.5,
|
||||||
|
opacity: 0.3,
|
||||||
|
lineWidth: 2,
|
||||||
|
color: 'rgba(255, 200, 0, 1)',
|
||||||
|
dashPattern: [8, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.33,
|
||||||
|
opacity: 0.6,
|
||||||
|
lineWidth: 2.5,
|
||||||
|
color: 'rgba(255, 200, 0, 1)',
|
||||||
|
dashPattern: [8, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radius: 0.167,
|
||||||
|
opacity: 0.9,
|
||||||
|
lineWidth: 3,
|
||||||
|
color: 'rgba(255, 200, 0, 1)',
|
||||||
|
dashPattern: [8, 4],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 원 내부 채우기
|
||||||
|
circleFillColor: 'rgba(255, 200, 0, 0.08)',
|
||||||
|
// 중심점
|
||||||
|
centerPoint: {
|
||||||
|
radius: 5,
|
||||||
|
fillColor: 'rgba(255, 200, 0, 1)',
|
||||||
|
strokeColor: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
strokeWidth: 2,
|
||||||
|
},
|
||||||
|
// 포인트 핸들
|
||||||
|
pointHandle: {
|
||||||
|
size: 16,
|
||||||
|
fillColor: '#00aaff',
|
||||||
|
strokeColor: 'white',
|
||||||
|
strokeWidth: 2,
|
||||||
|
labelColor: '#00aaff',
|
||||||
|
labelFontSize: 11,
|
||||||
|
},
|
||||||
|
// 영역 외곽선
|
||||||
|
areaOutline: {
|
||||||
|
selectedColor: '#00aaff',
|
||||||
|
unselectedColor: '#888',
|
||||||
|
selectedWidth: 2,
|
||||||
|
unselectedWidth: 1,
|
||||||
|
unselectedDashPattern: [5, 5],
|
||||||
|
selectedFillColor: 'rgba(0, 170, 255, 0.08)', // 선택된 영역 배경 (연한 파란색)
|
||||||
|
unselectedFillColor: 'rgba(136, 136, 136, 0.03)', // 선택 안된 영역 배경 (연한 회색)
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -7,6 +7,41 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 에디터 툴바 */
|
||||||
|
.editor-toolbar {
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toggle-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #383838;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: 2px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toggle-btn:hover {
|
||||||
|
background: #404040;
|
||||||
|
border-color: #00aaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toggle-btn.active {
|
||||||
|
background: #2d5a7a;
|
||||||
|
border-color: #00aaff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-main {
|
.editor-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user