feat: Add AreaList and ParameterPanel components
- 영역 목록과 파라미터 편집 패널을 추가하여 왜곡 영역 관리를 개선했습니다. - 각 영역의 강도, 애니메이션 지속 시간, 이징 함수 등을 조절할 수 있습니다. - 새 영역 추가 및 기존 영역 삭제 기능을 제공합니다.
This commit is contained in:
parent
6babf68c71
commit
5f6e780b40
59
dist/index.d.mts
vendored
59
dist/index.d.mts
vendored
@ -305,27 +305,39 @@ interface EditorCanvasStyle {
|
|||||||
/** 영역 외곽선 스타일 */
|
/** 영역 외곽선 스타일 */
|
||||||
areaOutline?: AreaOutlineStyle;
|
areaOutline?: AreaOutlineStyle;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 에디터 Props
|
|
||||||
*/
|
|
||||||
interface DistortionEditorProps {
|
|
||||||
/** 초기 영역 배열 */
|
|
||||||
initialAreas?: DistortionArea[];
|
|
||||||
/** 이미지 소스 */
|
|
||||||
imageSrc: string;
|
|
||||||
/** 영역 변경 콜백 */
|
|
||||||
onAreasChange?: (areas: DistortionArea[]) => void;
|
|
||||||
/** 선택된 영역 변경 콜백 */
|
|
||||||
onSelectedAreaChange?: (areaId: string | null) => void;
|
|
||||||
/** 캔버스 너비 */
|
|
||||||
width?: number;
|
|
||||||
/** 캔버스 높이 */
|
|
||||||
height?: number;
|
|
||||||
/** 에디터 캔버스 스타일 커스터마이징 */
|
|
||||||
canvasStyle?: EditorCanvasStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const DistortionEditor: React$1.FC<DistortionEditorProps>;
|
interface EditorCanvasProps {
|
||||||
|
areas: DistortionArea[];
|
||||||
|
selectedAreaId: string | null;
|
||||||
|
imageSrc: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
onUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;
|
||||||
|
onUpdateArea: (areaId: string, updates: Partial<DistortionArea>) => void;
|
||||||
|
draggingPointIndex: number | null;
|
||||||
|
onStartDragging: (pointIndex: number) => void;
|
||||||
|
onStopDragging: () => void;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
style?: EditorCanvasStyle;
|
||||||
|
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||||
|
showEditor?: boolean;
|
||||||
|
}
|
||||||
|
declare const EditorCanvas: React$1.FC<EditorCanvasProps>;
|
||||||
|
|
||||||
|
interface AreaListProps {
|
||||||
|
areas: DistortionArea[];
|
||||||
|
selectedAreaId: string | null;
|
||||||
|
onSelectArea: (areaId: string) => void;
|
||||||
|
onRemoveArea: (areaId: string) => void;
|
||||||
|
onAddArea: () => void;
|
||||||
|
}
|
||||||
|
declare const AreaList: React$1.FC<AreaListProps>;
|
||||||
|
|
||||||
|
interface ParameterPanelProps {
|
||||||
|
area: DistortionArea | null;
|
||||||
|
onUpdateArea: (updates: Partial<DistortionArea>) => void;
|
||||||
|
}
|
||||||
|
declare const ParameterPanel: React$1.FC<ParameterPanelProps>;
|
||||||
|
|
||||||
declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
||||||
state: EditorState;
|
state: EditorState;
|
||||||
@ -340,6 +352,11 @@ declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
|||||||
getSelectedArea: () => DistortionArea | null;
|
getSelectedArea: () => DistortionArea | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기본 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
declare const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 진행도에 이징 함수를 적용
|
* 진행도에 이징 함수를 적용
|
||||||
* @param progress 진행도 (0.0 - 1.0)
|
* @param progress 진행도 (0.0 - 1.0)
|
||||||
@ -569,4 +586,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
|||||||
getInteractingAreaIndices: () => Set<number>;
|
getInteractingAreaIndices: () => Set<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||||
|
|||||||
59
dist/index.d.ts
vendored
59
dist/index.d.ts
vendored
@ -305,27 +305,39 @@ interface EditorCanvasStyle {
|
|||||||
/** 영역 외곽선 스타일 */
|
/** 영역 외곽선 스타일 */
|
||||||
areaOutline?: AreaOutlineStyle;
|
areaOutline?: AreaOutlineStyle;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 에디터 Props
|
|
||||||
*/
|
|
||||||
interface DistortionEditorProps {
|
|
||||||
/** 초기 영역 배열 */
|
|
||||||
initialAreas?: DistortionArea[];
|
|
||||||
/** 이미지 소스 */
|
|
||||||
imageSrc: string;
|
|
||||||
/** 영역 변경 콜백 */
|
|
||||||
onAreasChange?: (areas: DistortionArea[]) => void;
|
|
||||||
/** 선택된 영역 변경 콜백 */
|
|
||||||
onSelectedAreaChange?: (areaId: string | null) => void;
|
|
||||||
/** 캔버스 너비 */
|
|
||||||
width?: number;
|
|
||||||
/** 캔버스 높이 */
|
|
||||||
height?: number;
|
|
||||||
/** 에디터 캔버스 스타일 커스터마이징 */
|
|
||||||
canvasStyle?: EditorCanvasStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const DistortionEditor: React$1.FC<DistortionEditorProps>;
|
interface EditorCanvasProps {
|
||||||
|
areas: DistortionArea[];
|
||||||
|
selectedAreaId: string | null;
|
||||||
|
imageSrc: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
onUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;
|
||||||
|
onUpdateArea: (areaId: string, updates: Partial<DistortionArea>) => void;
|
||||||
|
draggingPointIndex: number | null;
|
||||||
|
onStartDragging: (pointIndex: number) => void;
|
||||||
|
onStopDragging: () => void;
|
||||||
|
/** 에디터 캔버스 스타일 커스터마이징 */
|
||||||
|
style?: EditorCanvasStyle;
|
||||||
|
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||||
|
showEditor?: boolean;
|
||||||
|
}
|
||||||
|
declare const EditorCanvas: React$1.FC<EditorCanvasProps>;
|
||||||
|
|
||||||
|
interface AreaListProps {
|
||||||
|
areas: DistortionArea[];
|
||||||
|
selectedAreaId: string | null;
|
||||||
|
onSelectArea: (areaId: string) => void;
|
||||||
|
onRemoveArea: (areaId: string) => void;
|
||||||
|
onAddArea: () => void;
|
||||||
|
}
|
||||||
|
declare const AreaList: React$1.FC<AreaListProps>;
|
||||||
|
|
||||||
|
interface ParameterPanelProps {
|
||||||
|
area: DistortionArea | null;
|
||||||
|
onUpdateArea: (updates: Partial<DistortionArea>) => void;
|
||||||
|
}
|
||||||
|
declare const ParameterPanel: React$1.FC<ParameterPanelProps>;
|
||||||
|
|
||||||
declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
||||||
state: EditorState;
|
state: EditorState;
|
||||||
@ -340,6 +352,11 @@ declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => {
|
|||||||
getSelectedArea: () => DistortionArea | null;
|
getSelectedArea: () => DistortionArea | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기본 에디터 캔버스 스타일
|
||||||
|
*/
|
||||||
|
declare const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 진행도에 이징 함수를 적용
|
* 진행도에 이징 함수를 적용
|
||||||
* @param progress 진행도 (0.0 - 1.0)
|
* @param progress 진행도 (0.0 - 1.0)
|
||||||
@ -569,4 +586,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
|||||||
getInteractingAreaIndices: () => Set<number>;
|
getInteractingAreaIndices: () => Set<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||||
|
|||||||
582
dist/index.js
vendored
582
dist/index.js
vendored
@ -32,9 +32,12 @@ var index_exports = {};
|
|||||||
__export(index_exports, {
|
__export(index_exports, {
|
||||||
ANIMATION_CONFIG: () => ANIMATION_CONFIG,
|
ANIMATION_CONFIG: () => ANIMATION_CONFIG,
|
||||||
AnimationLoop: () => AnimationLoop,
|
AnimationLoop: () => AnimationLoop,
|
||||||
|
AreaList: () => AreaList,
|
||||||
DEFAULT_AREA: () => DEFAULT_AREA,
|
DEFAULT_AREA: () => DEFAULT_AREA,
|
||||||
DistortionEditor: () => DistortionEditor,
|
DEFAULT_EDITOR_CANVAS_STYLE: () => DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
EditorCanvas: () => EditorCanvas,
|
||||||
ImageDistortion: () => ImageDistortion,
|
ImageDistortion: () => ImageDistortion,
|
||||||
|
ParameterPanel: () => ParameterPanel,
|
||||||
SHADER_CONFIG: () => SHADER_CONFIG,
|
SHADER_CONFIG: () => SHADER_CONFIG,
|
||||||
ShaderManager: () => ShaderManager,
|
ShaderManager: () => ShaderManager,
|
||||||
SpringPhysics: () => SpringPhysics,
|
SpringPhysics: () => SpringPhysics,
|
||||||
@ -986,8 +989,152 @@ var ImageDistortion = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/DistortionEditor.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
var import_react7 = require("react");
|
var import_react6 = require("react");
|
||||||
|
|
||||||
|
// src/editor/components/AreaList.tsx
|
||||||
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
||||||
|
var AreaList = ({
|
||||||
|
areas,
|
||||||
|
selectedAreaId,
|
||||||
|
onSelectArea,
|
||||||
|
onRemoveArea,
|
||||||
|
onAddArea
|
||||||
|
}) => {
|
||||||
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "area-list", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "area-list-header", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: onAddArea,
|
||||||
|
disabled: areas.length >= 8,
|
||||||
|
className: "btn-add",
|
||||||
|
title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00",
|
||||||
|
children: "+ \uCD94\uAC00"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`,
|
||||||
|
onClick: () => onSelectArea(area.id),
|
||||||
|
children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "area-item-info", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "area-item-name", children: [
|
||||||
|
"\uC601\uC5ED ",
|
||||||
|
index + 1
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "area-item-strength", children: [
|
||||||
|
"\uAC15\uB3C4: ",
|
||||||
|
(area.distortionStrength * 100).toFixed(0),
|
||||||
|
"%"
|
||||||
|
] })
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemoveArea(area.id);
|
||||||
|
},
|
||||||
|
className: "btn-remove",
|
||||||
|
title: "\uC601\uC5ED \uC0AD\uC81C",
|
||||||
|
children: "\xD7"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
area.id
|
||||||
|
)) })
|
||||||
|
] });
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/editor/components/ParameterPanel.tsx
|
||||||
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
||||||
|
var EASING_OPTIONS = [
|
||||||
|
{ value: "linear", label: "\uC120\uD615 (Linear)" },
|
||||||
|
{ value: "easeIn", label: "\uAC00\uC18D (Ease In)" },
|
||||||
|
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
||||||
|
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
||||||
|
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
||||||
|
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
||||||
|
];
|
||||||
|
var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||||
|
if (!area) {
|
||||||
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "parameter-panel", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) });
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-panel", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { children: [
|
||||||
|
"\uC65C\uACE1 \uAC15\uB3C4: ",
|
||||||
|
(area.distortionStrength * 100).toFixed(0),
|
||||||
|
"%"
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
type: "range",
|
||||||
|
min: "0",
|
||||||
|
max: "1",
|
||||||
|
step: "0.01",
|
||||||
|
value: area.distortionStrength,
|
||||||
|
onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }),
|
||||||
|
className: "slider"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { children: [
|
||||||
|
"\uC9C0\uC18D \uC2DC\uAC04: ",
|
||||||
|
area.movement.duration.toFixed(1),
|
||||||
|
"\uCD08"
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
type: "number",
|
||||||
|
min: "0.1",
|
||||||
|
max: "10",
|
||||||
|
step: "0.1",
|
||||||
|
value: area.movement.duration,
|
||||||
|
onChange: (e) => onUpdateArea({
|
||||||
|
movement: { ...area.movement, duration: parseFloat(e.target.value) }
|
||||||
|
}),
|
||||||
|
className: "input-number"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "\uC774\uC9D5 \uD568\uC218" }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
value: area.movement.easing,
|
||||||
|
onChange: (e) => onUpdateArea({
|
||||||
|
movement: { ...area.movement, easing: e.target.value }
|
||||||
|
}),
|
||||||
|
className: "select",
|
||||||
|
children: EASING_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: option.value, children: option.label }, option.value))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
|
||||||
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "point-coord", children: [
|
||||||
|
"P",
|
||||||
|
idx + 1,
|
||||||
|
": (",
|
||||||
|
point.x.toFixed(3),
|
||||||
|
", ",
|
||||||
|
point.y.toFixed(3),
|
||||||
|
")"
|
||||||
|
] }, idx)) })
|
||||||
|
] })
|
||||||
|
] });
|
||||||
|
};
|
||||||
|
|
||||||
// src/editor/hooks/useDistortionEditor.ts
|
// src/editor/hooks/useDistortionEditor.ts
|
||||||
var import_react5 = require("react");
|
var import_react5 = require("react");
|
||||||
@ -1063,9 +1210,66 @@ var useDistortionEditor = (initialAreas = []) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/editor/constants.ts
|
||||||
|
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
||||||
|
// 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)"
|
||||||
|
// 선택 안된 영역 배경 (연한 회색)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/editor/components/EditorCanvas.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
var import_react6 = require("react");
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
||||||
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
||||||
var EditorCanvas = ({
|
var EditorCanvas = ({
|
||||||
areas,
|
areas,
|
||||||
selectedAreaId,
|
selectedAreaId,
|
||||||
@ -1274,7 +1478,7 @@ var EditorCanvas = ({
|
|||||||
if (isDraggingArea) return "grabbing";
|
if (isDraggingArea) return "grabbing";
|
||||||
return "default";
|
return "default";
|
||||||
};
|
};
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
ref: containerRef,
|
ref: containerRef,
|
||||||
@ -1293,8 +1497,8 @@ var EditorCanvas = ({
|
|||||||
onTouchStart: showEditor ? handleCanvasDown : void 0,
|
onTouchStart: showEditor ? handleCanvasDown : void 0,
|
||||||
onTouchMove: showEditor ? handleMove : void 0,
|
onTouchMove: showEditor ? handleMove : void 0,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }),
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ImageDistortion, { imageSrc, areas }),
|
||||||
showEditor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
showEditor && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1309,7 +1513,7 @@ var EditorCanvas = ({
|
|||||||
const isSelected = area.id === selectedAreaId;
|
const isSelected = area.id === selectedAreaId;
|
||||||
const points = area.basePoints;
|
const points = area.basePoints;
|
||||||
const outlineStyle = editorStyle.areaOutline || {};
|
const outlineStyle = editorStyle.areaOutline || {};
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
||||||
"polygon",
|
"polygon",
|
||||||
{
|
{
|
||||||
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
||||||
@ -1323,7 +1527,7 @@ var EditorCanvas = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
||||||
"canvas",
|
"canvas",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1349,7 +1553,7 @@ var EditorCanvas = ({
|
|||||||
),
|
),
|
||||||
showEditor && 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_runtime4.jsx)(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
||||||
@ -1369,7 +1573,7 @@ var EditorCanvas = ({
|
|||||||
},
|
},
|
||||||
onMouseDown: handlePointDown(index),
|
onMouseDown: handlePointDown(index),
|
||||||
onTouchStart: handlePointDown(index),
|
onTouchStart: handlePointDown(index),
|
||||||
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1397,362 +1601,16 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/components/AreaList.tsx
|
|
||||||
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
||||||
var AreaList = ({
|
|
||||||
areas,
|
|
||||||
selectedAreaId,
|
|
||||||
onSelectArea,
|
|
||||||
onRemoveArea,
|
|
||||||
onAddArea
|
|
||||||
}) => {
|
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-list", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-list-header", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: onAddArea,
|
|
||||||
disabled: areas.length >= 8,
|
|
||||||
className: "btn-add",
|
|
||||||
title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00",
|
|
||||||
children: "+ \uCD94\uAC00"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`,
|
|
||||||
onClick: () => onSelectArea(area.id),
|
|
||||||
children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-item-info", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "area-item-name", children: [
|
|
||||||
"\uC601\uC5ED ",
|
|
||||||
index + 1
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "area-item-strength", children: [
|
|
||||||
"\uAC15\uB3C4: ",
|
|
||||||
(area.distortionStrength * 100).toFixed(0),
|
|
||||||
"%"
|
|
||||||
] })
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onRemoveArea(area.id);
|
|
||||||
},
|
|
||||||
className: "btn-remove",
|
|
||||||
title: "\uC601\uC5ED \uC0AD\uC81C",
|
|
||||||
children: "\xD7"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
area.id
|
|
||||||
)) })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/components/ParameterPanel.tsx
|
|
||||||
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
||||||
var EASING_OPTIONS = [
|
|
||||||
{ value: "linear", label: "\uC120\uD615 (Linear)" },
|
|
||||||
{ value: "easeIn", label: "\uAC00\uC18D (Ease In)" },
|
|
||||||
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
|
||||||
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
|
||||||
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
|
||||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
|
||||||
];
|
|
||||||
var ParameterPanel = ({ area, onUpdateArea }) => {
|
|
||||||
if (!area) {
|
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "parameter-panel", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) });
|
|
||||||
}
|
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-panel", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [
|
|
||||||
"\uC65C\uACE1 \uAC15\uB3C4: ",
|
|
||||||
(area.distortionStrength * 100).toFixed(0),
|
|
||||||
"%"
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "0",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.distortionStrength,
|
|
||||||
onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [
|
|
||||||
"\uC9C0\uC18D \uC2DC\uAC04: ",
|
|
||||||
area.movement.duration.toFixed(1),
|
|
||||||
"\uCD08"
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "number",
|
|
||||||
min: "0.1",
|
|
||||||
max: "10",
|
|
||||||
step: "0.1",
|
|
||||||
value: area.movement.duration,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: { ...area.movement, duration: parseFloat(e.target.value) }
|
|
||||||
}),
|
|
||||||
className: "input-number"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "\uC774\uC9D5 \uD568\uC218" }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
||||||
"select",
|
|
||||||
{
|
|
||||||
value: area.movement.easing,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: { ...area.movement, easing: e.target.value }
|
|
||||||
}),
|
|
||||||
className: "select",
|
|
||||||
children: EASING_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: option.value, children: option.label }, option.value))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [
|
|
||||||
"\uBCA1\uD130 X: ",
|
|
||||||
area.movement.vectorA.x.toFixed(2)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "-1",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.movement.vectorA.x,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [
|
|
||||||
"\uBCA1\uD130 Y: ",
|
|
||||||
area.movement.vectorA.y.toFixed(2)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "-1",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.movement.vectorA.y,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "point-coord", children: [
|
|
||||||
"P",
|
|
||||||
idx + 1,
|
|
||||||
": (",
|
|
||||||
point.x.toFixed(3),
|
|
||||||
", ",
|
|
||||||
point.y.toFixed(3),
|
|
||||||
")"
|
|
||||||
] }, idx)) })
|
|
||||||
] })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/DistortionEditor.tsx
|
|
||||||
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
||||||
var DistortionEditor = ({
|
|
||||||
initialAreas = [],
|
|
||||||
imageSrc,
|
|
||||||
onAreasChange,
|
|
||||||
onSelectedAreaChange,
|
|
||||||
width = 800,
|
|
||||||
height = 600,
|
|
||||||
canvasStyle
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
state,
|
|
||||||
selectArea,
|
|
||||||
addArea,
|
|
||||||
removeArea,
|
|
||||||
updateArea,
|
|
||||||
updatePoint,
|
|
||||||
startDragging,
|
|
||||||
stopDragging,
|
|
||||||
getSelectedArea
|
|
||||||
} = useDistortionEditor(initialAreas);
|
|
||||||
const [showEditor, setShowEditor] = (0, import_react7.useState)(true);
|
|
||||||
(0, import_react7.useEffect)(() => {
|
|
||||||
onAreasChange?.(state.areas);
|
|
||||||
}, [state.areas, onAreasChange]);
|
|
||||||
(0, import_react7.useEffect)(() => {
|
|
||||||
onSelectedAreaChange?.(state.selectedAreaId);
|
|
||||||
}, [state.selectedAreaId, onSelectedAreaChange]);
|
|
||||||
const handleAddArea = () => {
|
|
||||||
const newArea = {
|
|
||||||
id: `area-${Date.now()}`,
|
|
||||||
basePoints: [
|
|
||||||
{ x: 0.3, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.7 },
|
|
||||||
{ x: 0.3, y: 0.7 }
|
|
||||||
],
|
|
||||||
movement: {
|
|
||||||
vectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },
|
|
||||||
vectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },
|
|
||||||
duration: DEFAULT_AREA.DURATION,
|
|
||||||
easing: DEFAULT_AREA.EASING
|
|
||||||
},
|
|
||||||
distortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,
|
|
||||||
progress: 0,
|
|
||||||
dragVector: { x: 0, y: 0 }
|
|
||||||
};
|
|
||||||
addArea(newArea);
|
|
||||||
};
|
|
||||||
const handleUpdateArea = (updates) => {
|
|
||||||
if (state.selectedAreaId) {
|
|
||||||
updateArea(state.selectedAreaId, updates);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const selectedArea = getSelectedArea();
|
|
||||||
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)(
|
|
||||||
EditorCanvas,
|
|
||||||
{
|
|
||||||
areas: state.areas,
|
|
||||||
selectedAreaId: state.selectedAreaId,
|
|
||||||
imageSrc,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
onUpdatePoint: updatePoint,
|
|
||||||
onUpdateArea: updateArea,
|
|
||||||
draggingPointIndex: state.draggingPointIndex,
|
|
||||||
onStartDragging: startDragging,
|
|
||||||
onStopDragging: stopDragging,
|
|
||||||
style: canvasStyle,
|
|
||||||
showEditor
|
|
||||||
}
|
|
||||||
) }),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
||||||
AreaList,
|
|
||||||
{
|
|
||||||
areas: state.areas,
|
|
||||||
selectedAreaId: state.selectedAreaId,
|
|
||||||
onSelectArea: selectArea,
|
|
||||||
onRemoveArea: removeArea,
|
|
||||||
onAddArea: handleAddArea
|
|
||||||
}
|
|
||||||
),
|
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
|
||||||
] })
|
|
||||||
] })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/constants.ts
|
|
||||||
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
|
||||||
// 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)"
|
|
||||||
// 선택 안된 영역 배경 (연한 회색)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 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 = {
|
||||||
ANIMATION_CONFIG,
|
ANIMATION_CONFIG,
|
||||||
AnimationLoop,
|
AnimationLoop,
|
||||||
|
AreaList,
|
||||||
DEFAULT_AREA,
|
DEFAULT_AREA,
|
||||||
DistortionEditor,
|
DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
EditorCanvas,
|
||||||
ImageDistortion,
|
ImageDistortion,
|
||||||
|
ParameterPanel,
|
||||||
SHADER_CONFIG,
|
SHADER_CONFIG,
|
||||||
ShaderManager,
|
ShaderManager,
|
||||||
SpringPhysics,
|
SpringPhysics,
|
||||||
|
|||||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
577
dist/index.mjs
vendored
577
dist/index.mjs
vendored
@ -935,8 +935,152 @@ var ImageDistortion = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/DistortionEditor.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
import { useEffect as useEffect5, useState as useState5 } from "react";
|
import { useRef as useRef5, useEffect as useEffect4, useState as useState4, useCallback as useCallback5, useMemo } from "react";
|
||||||
|
|
||||||
|
// src/editor/components/AreaList.tsx
|
||||||
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
||||||
|
var AreaList = ({
|
||||||
|
areas,
|
||||||
|
selectedAreaId,
|
||||||
|
onSelectArea,
|
||||||
|
onRemoveArea,
|
||||||
|
onAddArea
|
||||||
|
}) => {
|
||||||
|
return /* @__PURE__ */ jsxs("div", { className: "area-list", children: [
|
||||||
|
/* @__PURE__ */ jsxs("div", { className: "area-list-header", children: [
|
||||||
|
/* @__PURE__ */ jsx2("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }),
|
||||||
|
/* @__PURE__ */ jsx2(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: onAddArea,
|
||||||
|
disabled: areas.length >= 8,
|
||||||
|
className: "btn-add",
|
||||||
|
title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00",
|
||||||
|
children: "+ \uCD94\uAC00"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsx2("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ jsxs(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`,
|
||||||
|
onClick: () => onSelectArea(area.id),
|
||||||
|
children: [
|
||||||
|
/* @__PURE__ */ jsxs("div", { className: "area-item-info", children: [
|
||||||
|
/* @__PURE__ */ jsxs("span", { className: "area-item-name", children: [
|
||||||
|
"\uC601\uC5ED ",
|
||||||
|
index + 1
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsxs("span", { className: "area-item-strength", children: [
|
||||||
|
"\uAC15\uB3C4: ",
|
||||||
|
(area.distortionStrength * 100).toFixed(0),
|
||||||
|
"%"
|
||||||
|
] })
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsx2(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemoveArea(area.id);
|
||||||
|
},
|
||||||
|
className: "btn-remove",
|
||||||
|
title: "\uC601\uC5ED \uC0AD\uC81C",
|
||||||
|
children: "\xD7"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
area.id
|
||||||
|
)) })
|
||||||
|
] });
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/editor/components/ParameterPanel.tsx
|
||||||
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
||||||
|
var EASING_OPTIONS = [
|
||||||
|
{ value: "linear", label: "\uC120\uD615 (Linear)" },
|
||||||
|
{ value: "easeIn", label: "\uAC00\uC18D (Ease In)" },
|
||||||
|
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
||||||
|
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
||||||
|
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
||||||
|
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
||||||
|
];
|
||||||
|
var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||||
|
if (!area) {
|
||||||
|
return /* @__PURE__ */ jsx3("div", { className: "parameter-panel", children: /* @__PURE__ */ jsx3("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) });
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ jsxs2("div", { className: "parameter-panel", children: [
|
||||||
|
/* @__PURE__ */ jsx3("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }),
|
||||||
|
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ jsxs2("label", { children: [
|
||||||
|
"\uC65C\uACE1 \uAC15\uB3C4: ",
|
||||||
|
(area.distortionStrength * 100).toFixed(0),
|
||||||
|
"%"
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsx3(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
type: "range",
|
||||||
|
min: "0",
|
||||||
|
max: "1",
|
||||||
|
step: "0.01",
|
||||||
|
value: area.distortionStrength,
|
||||||
|
onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }),
|
||||||
|
className: "slider"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ jsxs2("label", { children: [
|
||||||
|
"\uC9C0\uC18D \uC2DC\uAC04: ",
|
||||||
|
area.movement.duration.toFixed(1),
|
||||||
|
"\uCD08"
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsx3(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
type: "number",
|
||||||
|
min: "0.1",
|
||||||
|
max: "10",
|
||||||
|
step: "0.1",
|
||||||
|
value: area.movement.duration,
|
||||||
|
onChange: (e) => onUpdateArea({
|
||||||
|
movement: { ...area.movement, duration: parseFloat(e.target.value) }
|
||||||
|
}),
|
||||||
|
className: "input-number"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ jsx3("label", { children: "\uC774\uC9D5 \uD568\uC218" }),
|
||||||
|
/* @__PURE__ */ jsx3(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
value: area.movement.easing,
|
||||||
|
onChange: (e) => onUpdateArea({
|
||||||
|
movement: { ...area.movement, easing: e.target.value }
|
||||||
|
}),
|
||||||
|
className: "select",
|
||||||
|
children: EASING_OPTIONS.map((option) => /* @__PURE__ */ jsx3("option", { value: option.value, children: option.label }, option.value))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
] }),
|
||||||
|
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
|
||||||
|
/* @__PURE__ */ jsx3("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
|
||||||
|
/* @__PURE__ */ jsx3("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs2("div", { className: "point-coord", children: [
|
||||||
|
"P",
|
||||||
|
idx + 1,
|
||||||
|
": (",
|
||||||
|
point.x.toFixed(3),
|
||||||
|
", ",
|
||||||
|
point.y.toFixed(3),
|
||||||
|
")"
|
||||||
|
] }, idx)) })
|
||||||
|
] })
|
||||||
|
] });
|
||||||
|
};
|
||||||
|
|
||||||
// src/editor/hooks/useDistortionEditor.ts
|
// src/editor/hooks/useDistortionEditor.ts
|
||||||
import { useState as useState3, useCallback as useCallback4 } from "react";
|
import { useState as useState3, useCallback as useCallback4 } from "react";
|
||||||
@ -1012,9 +1156,66 @@ var useDistortionEditor = (initialAreas = []) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/editor/constants.ts
|
||||||
|
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
||||||
|
// 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)"
|
||||||
|
// 선택 안된 영역 배경 (연한 회색)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/editor/components/EditorCanvas.tsx
|
// src/editor/components/EditorCanvas.tsx
|
||||||
import { useRef as useRef5, useEffect as useEffect4, useState as useState4, useCallback as useCallback5, useMemo } from "react";
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
||||||
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
||||||
var EditorCanvas = ({
|
var EditorCanvas = ({
|
||||||
areas,
|
areas,
|
||||||
selectedAreaId,
|
selectedAreaId,
|
||||||
@ -1223,7 +1424,7 @@ var EditorCanvas = ({
|
|||||||
if (isDraggingArea) return "grabbing";
|
if (isDraggingArea) return "grabbing";
|
||||||
return "default";
|
return "default";
|
||||||
};
|
};
|
||||||
return /* @__PURE__ */ jsxs(
|
return /* @__PURE__ */ jsxs3(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
ref: containerRef,
|
ref: containerRef,
|
||||||
@ -1242,8 +1443,8 @@ var EditorCanvas = ({
|
|||||||
onTouchStart: showEditor ? handleCanvasDown : void 0,
|
onTouchStart: showEditor ? handleCanvasDown : void 0,
|
||||||
onTouchMove: showEditor ? handleMove : void 0,
|
onTouchMove: showEditor ? handleMove : void 0,
|
||||||
children: [
|
children: [
|
||||||
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }),
|
/* @__PURE__ */ jsx4(ImageDistortion, { imageSrc, areas }),
|
||||||
showEditor && /* @__PURE__ */ jsx2(
|
showEditor && /* @__PURE__ */ jsx4(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1258,7 +1459,7 @@ var EditorCanvas = ({
|
|||||||
const isSelected = area.id === selectedAreaId;
|
const isSelected = area.id === selectedAreaId;
|
||||||
const points = area.basePoints;
|
const points = area.basePoints;
|
||||||
const outlineStyle = editorStyle.areaOutline || {};
|
const outlineStyle = editorStyle.areaOutline || {};
|
||||||
return /* @__PURE__ */ jsx2("g", { children: /* @__PURE__ */ jsx2(
|
return /* @__PURE__ */ jsx4("g", { children: /* @__PURE__ */ jsx4(
|
||||||
"polygon",
|
"polygon",
|
||||||
{
|
{
|
||||||
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "),
|
||||||
@ -1272,7 +1473,7 @@ var EditorCanvas = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ jsx2(
|
showEditor && selectedArea && canvasSize.width > 0 && /* @__PURE__ */ jsx4(
|
||||||
"canvas",
|
"canvas",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1298,7 +1499,7 @@ var EditorCanvas = ({
|
|||||||
),
|
),
|
||||||
showEditor && 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__ */ jsx4(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`,
|
||||||
@ -1318,7 +1519,7 @@ var EditorCanvas = ({
|
|||||||
},
|
},
|
||||||
onMouseDown: handlePointDown(index),
|
onMouseDown: handlePointDown(index),
|
||||||
onTouchStart: handlePointDown(index),
|
onTouchStart: handlePointDown(index),
|
||||||
children: /* @__PURE__ */ jsxs(
|
children: /* @__PURE__ */ jsxs3(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -1346,361 +1547,15 @@ var EditorCanvas = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/editor/components/AreaList.tsx
|
|
||||||
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
||||||
var AreaList = ({
|
|
||||||
areas,
|
|
||||||
selectedAreaId,
|
|
||||||
onSelectArea,
|
|
||||||
onRemoveArea,
|
|
||||||
onAddArea
|
|
||||||
}) => {
|
|
||||||
return /* @__PURE__ */ jsxs2("div", { className: "area-list", children: [
|
|
||||||
/* @__PURE__ */ jsxs2("div", { className: "area-list-header", children: [
|
|
||||||
/* @__PURE__ */ jsx3("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }),
|
|
||||||
/* @__PURE__ */ jsx3(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: onAddArea,
|
|
||||||
disabled: areas.length >= 8,
|
|
||||||
className: "btn-add",
|
|
||||||
title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00",
|
|
||||||
children: "+ \uCD94\uAC00"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx3("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ jsxs2(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`,
|
|
||||||
onClick: () => onSelectArea(area.id),
|
|
||||||
children: [
|
|
||||||
/* @__PURE__ */ jsxs2("div", { className: "area-item-info", children: [
|
|
||||||
/* @__PURE__ */ jsxs2("span", { className: "area-item-name", children: [
|
|
||||||
"\uC601\uC5ED ",
|
|
||||||
index + 1
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs2("span", { className: "area-item-strength", children: [
|
|
||||||
"\uAC15\uB3C4: ",
|
|
||||||
(area.distortionStrength * 100).toFixed(0),
|
|
||||||
"%"
|
|
||||||
] })
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx3(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onRemoveArea(area.id);
|
|
||||||
},
|
|
||||||
className: "btn-remove",
|
|
||||||
title: "\uC601\uC5ED \uC0AD\uC81C",
|
|
||||||
children: "\xD7"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
area.id
|
|
||||||
)) })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/components/ParameterPanel.tsx
|
|
||||||
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
||||||
var EASING_OPTIONS = [
|
|
||||||
{ value: "linear", label: "\uC120\uD615 (Linear)" },
|
|
||||||
{ value: "easeIn", label: "\uAC00\uC18D (Ease In)" },
|
|
||||||
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
|
||||||
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
|
||||||
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
|
||||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
|
||||||
];
|
|
||||||
var ParameterPanel = ({ area, onUpdateArea }) => {
|
|
||||||
if (!area) {
|
|
||||||
return /* @__PURE__ */ jsx4("div", { className: "parameter-panel", children: /* @__PURE__ */ jsx4("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) });
|
|
||||||
}
|
|
||||||
return /* @__PURE__ */ jsxs3("div", { className: "parameter-panel", children: [
|
|
||||||
/* @__PURE__ */ jsx4("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsxs3("label", { children: [
|
|
||||||
"\uC65C\uACE1 \uAC15\uB3C4: ",
|
|
||||||
(area.distortionStrength * 100).toFixed(0),
|
|
||||||
"%"
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx4(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "0",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.distortionStrength,
|
|
||||||
onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsxs3("label", { children: [
|
|
||||||
"\uC9C0\uC18D \uC2DC\uAC04: ",
|
|
||||||
area.movement.duration.toFixed(1),
|
|
||||||
"\uCD08"
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx4(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "number",
|
|
||||||
min: "0.1",
|
|
||||||
max: "10",
|
|
||||||
step: "0.1",
|
|
||||||
value: area.movement.duration,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: { ...area.movement, duration: parseFloat(e.target.value) }
|
|
||||||
}),
|
|
||||||
className: "input-number"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsx4("label", { children: "\uC774\uC9D5 \uD568\uC218" }),
|
|
||||||
/* @__PURE__ */ jsx4(
|
|
||||||
"select",
|
|
||||||
{
|
|
||||||
value: area.movement.easing,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: { ...area.movement, easing: e.target.value }
|
|
||||||
}),
|
|
||||||
className: "select",
|
|
||||||
children: EASING_OPTIONS.map((option) => /* @__PURE__ */ jsx4("option", { value: option.value, children: option.label }, option.value))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsxs3("label", { children: [
|
|
||||||
"\uBCA1\uD130 X: ",
|
|
||||||
area.movement.vectorA.x.toFixed(2)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx4(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "-1",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.movement.vectorA.x,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsxs3("label", { children: [
|
|
||||||
"\uBCA1\uD130 Y: ",
|
|
||||||
area.movement.vectorA.y.toFixed(2)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsx4(
|
|
||||||
"input",
|
|
||||||
{
|
|
||||||
type: "range",
|
|
||||||
min: "-1",
|
|
||||||
max: "1",
|
|
||||||
step: "0.01",
|
|
||||||
value: area.movement.vectorA.y,
|
|
||||||
onChange: (e) => onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
className: "slider"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] }),
|
|
||||||
/* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [
|
|
||||||
/* @__PURE__ */ jsx4("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
|
|
||||||
/* @__PURE__ */ jsx4("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs3("div", { className: "point-coord", children: [
|
|
||||||
"P",
|
|
||||||
idx + 1,
|
|
||||||
": (",
|
|
||||||
point.x.toFixed(3),
|
|
||||||
", ",
|
|
||||||
point.y.toFixed(3),
|
|
||||||
")"
|
|
||||||
] }, idx)) })
|
|
||||||
] })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/DistortionEditor.tsx
|
|
||||||
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
||||||
var DistortionEditor = ({
|
|
||||||
initialAreas = [],
|
|
||||||
imageSrc,
|
|
||||||
onAreasChange,
|
|
||||||
onSelectedAreaChange,
|
|
||||||
width = 800,
|
|
||||||
height = 600,
|
|
||||||
canvasStyle
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
state,
|
|
||||||
selectArea,
|
|
||||||
addArea,
|
|
||||||
removeArea,
|
|
||||||
updateArea,
|
|
||||||
updatePoint,
|
|
||||||
startDragging,
|
|
||||||
stopDragging,
|
|
||||||
getSelectedArea
|
|
||||||
} = useDistortionEditor(initialAreas);
|
|
||||||
const [showEditor, setShowEditor] = useState5(true);
|
|
||||||
useEffect5(() => {
|
|
||||||
onAreasChange?.(state.areas);
|
|
||||||
}, [state.areas, onAreasChange]);
|
|
||||||
useEffect5(() => {
|
|
||||||
onSelectedAreaChange?.(state.selectedAreaId);
|
|
||||||
}, [state.selectedAreaId, onSelectedAreaChange]);
|
|
||||||
const handleAddArea = () => {
|
|
||||||
const newArea = {
|
|
||||||
id: `area-${Date.now()}`,
|
|
||||||
basePoints: [
|
|
||||||
{ x: 0.3, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.7 },
|
|
||||||
{ x: 0.3, y: 0.7 }
|
|
||||||
],
|
|
||||||
movement: {
|
|
||||||
vectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },
|
|
||||||
vectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },
|
|
||||||
duration: DEFAULT_AREA.DURATION,
|
|
||||||
easing: DEFAULT_AREA.EASING
|
|
||||||
},
|
|
||||||
distortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,
|
|
||||||
progress: 0,
|
|
||||||
dragVector: { x: 0, y: 0 }
|
|
||||||
};
|
|
||||||
addArea(newArea);
|
|
||||||
};
|
|
||||||
const handleUpdateArea = (updates) => {
|
|
||||||
if (state.selectedAreaId) {
|
|
||||||
updateArea(state.selectedAreaId, updates);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const selectedArea = getSelectedArea();
|
|
||||||
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(
|
|
||||||
EditorCanvas,
|
|
||||||
{
|
|
||||||
areas: state.areas,
|
|
||||||
selectedAreaId: state.selectedAreaId,
|
|
||||||
imageSrc,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
onUpdatePoint: updatePoint,
|
|
||||||
onUpdateArea: updateArea,
|
|
||||||
draggingPointIndex: state.draggingPointIndex,
|
|
||||||
onStartDragging: startDragging,
|
|
||||||
onStopDragging: stopDragging,
|
|
||||||
style: canvasStyle,
|
|
||||||
showEditor
|
|
||||||
}
|
|
||||||
) }),
|
|
||||||
/* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [
|
|
||||||
/* @__PURE__ */ jsx5(
|
|
||||||
AreaList,
|
|
||||||
{
|
|
||||||
areas: state.areas,
|
|
||||||
selectedAreaId: state.selectedAreaId,
|
|
||||||
onSelectArea: selectArea,
|
|
||||||
onRemoveArea: removeArea,
|
|
||||||
onAddArea: handleAddArea
|
|
||||||
}
|
|
||||||
),
|
|
||||||
/* @__PURE__ */ jsx5(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea })
|
|
||||||
] })
|
|
||||||
] })
|
|
||||||
] });
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/editor/constants.ts
|
|
||||||
var DEFAULT_EDITOR_CANVAS_STYLE = {
|
|
||||||
// 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)"
|
|
||||||
// 선택 안된 영역 배경 (연한 회색)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export {
|
export {
|
||||||
ANIMATION_CONFIG,
|
ANIMATION_CONFIG,
|
||||||
AnimationLoop,
|
AnimationLoop,
|
||||||
|
AreaList,
|
||||||
DEFAULT_AREA,
|
DEFAULT_AREA,
|
||||||
DistortionEditor,
|
DEFAULT_EDITOR_CANVAS_STYLE,
|
||||||
|
EditorCanvas,
|
||||||
ImageDistortion,
|
ImageDistortion,
|
||||||
|
ParameterPanel,
|
||||||
SHADER_CONFIG,
|
SHADER_CONFIG,
|
||||||
ShaderManager,
|
ShaderManager,
|
||||||
SpringPhysics,
|
SpringPhysics,
|
||||||
|
|||||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@baekryang/responsive-image-canvas",
|
"name": "@baekryang/responsive-image-canvas",
|
||||||
"version": "1.0.3",
|
"version": "1.0.5",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import { DistortionArea } from '../types/area';
|
|
||||||
import { DistortionEditorProps } from './types';
|
|
||||||
import { useDistortionEditor } from './hooks/useDistortionEditor';
|
|
||||||
import { EditorCanvas } from './components/EditorCanvas';
|
|
||||||
import { AreaList } from './components/AreaList';
|
|
||||||
import { ParameterPanel } from './components/ParameterPanel';
|
|
||||||
import { DEFAULT_AREA } from '../utils/constants';
|
|
||||||
|
|
||||||
export const DistortionEditor: React.FC<DistortionEditorProps> = ({
|
|
||||||
initialAreas = [],
|
|
||||||
imageSrc,
|
|
||||||
onAreasChange,
|
|
||||||
onSelectedAreaChange,
|
|
||||||
width = 800,
|
|
||||||
height = 600,
|
|
||||||
canvasStyle,
|
|
||||||
showEditor = true,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
state,
|
|
||||||
selectArea,
|
|
||||||
addArea,
|
|
||||||
removeArea,
|
|
||||||
updateArea,
|
|
||||||
updatePoint,
|
|
||||||
startDragging,
|
|
||||||
stopDragging,
|
|
||||||
getSelectedArea,
|
|
||||||
} = useDistortionEditor(initialAreas);
|
|
||||||
|
|
||||||
// 영역 변경 시 콜백 호출
|
|
||||||
useEffect(() => {
|
|
||||||
onAreasChange?.(state.areas);
|
|
||||||
}, [state.areas, onAreasChange]);
|
|
||||||
|
|
||||||
// 선택된 영역 변경 시 콜백 호출
|
|
||||||
useEffect(() => {
|
|
||||||
onSelectedAreaChange?.(state.selectedAreaId);
|
|
||||||
}, [state.selectedAreaId, onSelectedAreaChange]);
|
|
||||||
|
|
||||||
// 새 영역 추가 핸들러
|
|
||||||
const handleAddArea = () => {
|
|
||||||
const newArea: DistortionArea = {
|
|
||||||
id: `area-${Date.now()}`,
|
|
||||||
basePoints: [
|
|
||||||
{ x: 0.3, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.3 },
|
|
||||||
{ x: 0.7, y: 0.7 },
|
|
||||||
{ x: 0.3, y: 0.7 },
|
|
||||||
],
|
|
||||||
movement: {
|
|
||||||
vectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },
|
|
||||||
vectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },
|
|
||||||
duration: DEFAULT_AREA.DURATION,
|
|
||||||
easing: DEFAULT_AREA.EASING as any,
|
|
||||||
},
|
|
||||||
distortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,
|
|
||||||
progress: 0,
|
|
||||||
dragVector: { x: 0, y: 0 },
|
|
||||||
};
|
|
||||||
addArea(newArea);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 파라미터 업데이트 핸들러
|
|
||||||
const handleUpdateArea = (updates: Partial<DistortionArea>) => {
|
|
||||||
if (state.selectedAreaId) {
|
|
||||||
updateArea(state.selectedAreaId, updates);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedArea = getSelectedArea();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="distortion-editor">
|
|
||||||
<div className="editor-main">
|
|
||||||
{/* 왼쪽: 캔버스 */}
|
|
||||||
<div className="editor-canvas-container">
|
|
||||||
<EditorCanvas
|
|
||||||
areas={state.areas}
|
|
||||||
selectedAreaId={state.selectedAreaId}
|
|
||||||
imageSrc={imageSrc}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
onUpdatePoint={updatePoint}
|
|
||||||
onUpdateArea={updateArea}
|
|
||||||
draggingPointIndex={state.draggingPointIndex}
|
|
||||||
onStartDragging={startDragging}
|
|
||||||
onStopDragging={stopDragging}
|
|
||||||
style={canvasStyle}
|
|
||||||
showEditor={showEditor}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 오른쪽: 사이드바 */}
|
|
||||||
<div className="editor-sidebar">
|
|
||||||
{/* 영역 목록 */}
|
|
||||||
<AreaList
|
|
||||||
areas={state.areas}
|
|
||||||
selectedAreaId={state.selectedAreaId}
|
|
||||||
onSelectArea={selectArea}
|
|
||||||
onRemoveArea={removeArea}
|
|
||||||
onAddArea={handleAddArea}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 파라미터 패널 */}
|
|
||||||
<ParameterPanel area={selectedArea} onUpdateArea={handleUpdateArea} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DistortionArea } from '../../types/area';
|
import { DistortionArea } from '../../types/area';
|
||||||
|
|
||||||
interface AreaListProps {
|
export interface AreaListProps {
|
||||||
areas: DistortionArea[];
|
areas: DistortionArea[];
|
||||||
selectedAreaId: string | null;
|
selectedAreaId: string | null;
|
||||||
onSelectArea: (areaId: string) => void;
|
onSelectArea: (areaId: string) => void;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {ImageDistortion} from '@/components/ImageDistortion';
|
|||||||
import {EditorCanvasStyle} from '../types';
|
import {EditorCanvasStyle} from '../types';
|
||||||
import {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';
|
import {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';
|
||||||
|
|
||||||
interface EditorCanvasProps {
|
export interface EditorCanvasProps {
|
||||||
areas: DistortionArea[];
|
areas: DistortionArea[];
|
||||||
selectedAreaId: string | null;
|
selectedAreaId: string | null;
|
||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DistortionArea, EasingFunction } from '../../types/area';
|
import { DistortionArea, EasingFunction } from '../../types/area';
|
||||||
|
|
||||||
interface ParameterPanelProps {
|
export interface ParameterPanelProps {
|
||||||
area: DistortionArea | null;
|
area: DistortionArea | null;
|
||||||
onUpdateArea: (updates: Partial<DistortionArea>) => void;
|
onUpdateArea: (updates: Partial<DistortionArea>) => void;
|
||||||
}
|
}
|
||||||
@ -84,52 +84,6 @@ export const ParameterPanel: React.FC<ParameterPanelProps> = ({ area, onUpdateAr
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 벡터 A (X) */}
|
|
||||||
<div className="parameter-group">
|
|
||||||
<label>
|
|
||||||
벡터 X: {area.movement.vectorA.x.toFixed(2)}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="-1"
|
|
||||||
max="1"
|
|
||||||
step="0.01"
|
|
||||||
value={area.movement.vectorA.x}
|
|
||||||
onChange={(e) =>
|
|
||||||
onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="slider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 벡터 A (Y) */}
|
|
||||||
<div className="parameter-group">
|
|
||||||
<label>
|
|
||||||
벡터 Y: {area.movement.vectorA.y.toFixed(2)}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="-1"
|
|
||||||
max="1"
|
|
||||||
step="0.01"
|
|
||||||
value={area.movement.vectorA.y}
|
|
||||||
onChange={(e) =>
|
|
||||||
onUpdateArea({
|
|
||||||
movement: {
|
|
||||||
...area.movement,
|
|
||||||
vectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="slider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 포인트 좌표 (읽기 전용 표시) */}
|
{/* 포인트 좌표 (읽기 전용 표시) */}
|
||||||
<div className="parameter-group">
|
<div className="parameter-group">
|
||||||
<label>포인트 좌표 (캔버스에서 드래그)</label>
|
<label>포인트 좌표 (캔버스에서 드래그)</label>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
export { DistortionEditor } from './DistortionEditor';
|
export { EditorCanvas } from './components/EditorCanvas';
|
||||||
|
export { AreaList } from './components/AreaList';
|
||||||
|
export { ParameterPanel } from './components/ParameterPanel';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
DistortionEditorProps,
|
|
||||||
EditorState,
|
EditorState,
|
||||||
EditMode,
|
EditMode,
|
||||||
EditorCanvasStyle,
|
EditorCanvasStyle,
|
||||||
|
|||||||
28
src/index.ts
28
src/index.ts
@ -2,10 +2,30 @@
|
|||||||
export { ImageDistortion } from './components/ImageDistortion';
|
export { ImageDistortion } from './components/ImageDistortion';
|
||||||
export type { ImageDistortionProps } from './components/ImageDistortion';
|
export type { ImageDistortionProps } from './components/ImageDistortion';
|
||||||
|
|
||||||
// 에디터 (4점 사각형 + 정확한 UV 좌표계 원형 왜곡 가이드)
|
// 에디터 컴포넌트들 (개별적으로 조합 가능)
|
||||||
export { DistortionEditor } from './editor';
|
export { EditorCanvas } from './editor/components/EditorCanvas';
|
||||||
export type { DistortionEditorProps, EditorState, EditMode } from './editor';
|
export type { EditorCanvasProps } from './editor/components/EditorCanvas';
|
||||||
export { useDistortionEditor } from './editor';
|
export { AreaList } from './editor/components/AreaList';
|
||||||
|
export type { AreaListProps } from './editor/components/AreaList';
|
||||||
|
export { ParameterPanel } from './editor/components/ParameterPanel';
|
||||||
|
export type { ParameterPanelProps } from './editor/components/ParameterPanel';
|
||||||
|
|
||||||
|
// 에디터 상태 관리 훅
|
||||||
|
export { useDistortionEditor } from './editor/hooks/useDistortionEditor';
|
||||||
|
|
||||||
|
// 에디터 타입 및 스타일
|
||||||
|
export type {
|
||||||
|
EditorState,
|
||||||
|
EditMode,
|
||||||
|
EditorCanvasStyle,
|
||||||
|
CircleLevelStyle,
|
||||||
|
CenterPointStyle,
|
||||||
|
PointHandleStyle,
|
||||||
|
AreaOutlineStyle,
|
||||||
|
} from './editor/types';
|
||||||
|
|
||||||
|
// 에디터 기본 스타일 상수
|
||||||
|
export { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants';
|
||||||
|
|
||||||
// 타입 정의
|
// 타입 정의
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user