Files
goldenChart/frontend/src/components/DrawingToolbar.tsx
T
2026-05-23 15:11:48 +09:00

628 lines
44 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import type { ChartMode } from '../types';
export interface DrawingToolbarProps {
activeTool: string;
mode: ChartMode;
onTool: (tool: string) => void;
onClearAll: () => void;
onUndo: () => void;
locked: boolean;
visible: boolean;
onToggleLock: () => void;
onToggleEye: () => void;
// 신규 확장 props (optional — App.tsx 점진 적용)
indicatorsVisible?: boolean;
magnetMode?: 'off' | 'weak' | 'strong';
snapToIndicator?: boolean;
drawingCount?: number;
indicatorCount?: number;
onMagnetMode?: (m: 'off' | 'weak' | 'strong') => void;
onSnapToIndicator?: (v: boolean) => void;
onHideDrawings?: () => void;
onHideIndicators?: () => void;
onHideAll?: () => void;
onClearIndicators?: () => void;
keepLockedOnDelete?: boolean;
onToggleKeepLocked?: () => void;
}
// ─── SVG 헬퍼 ──────────────────────────────────────────────────────────────
const S = (p: React.SVGProps<SVGSVGElement>) =>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"
stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" {...p}/>;
// ─── 아이콘 ────────────────────────────────────────────────────────────────
const IcCursor = () => <S><path d="M3 2L3 14L6.5 10.5L9.5 16L11 15.3L8 9.5L12.5 9.5Z" strokeWidth="1.4" fill="currentColor" fillOpacity="0.15"/></S>;
// 업비트 즐겨찾기 툴바 아이콘
const IcUpbitHline = () => <S strokeWidth="1.5"><line x1="2" y1="9" x2="16" y2="9"/><circle cx="9" cy="9" r="1.6" fill="currentColor" stroke="none"/></S>;
const IcUpbitVline = () => <S strokeWidth="1.5"><line x1="9" y1="2" x2="9" y2="16"/><circle cx="9" cy="9" r="1.6" fill="currentColor" stroke="none"/></S>;
const IcUpbitPeriod = () => <S strokeWidth="1.4"><line x1="5" y1="3" x2="5" y2="15"/><line x1="13" y1="3" x2="13" y2="15"/><circle cx="5" cy="9" r="1.4" fill="currentColor" stroke="none"/><circle cx="13" cy="9" r="1.4" fill="currentColor" stroke="none"/><line x1="6.5" y1="9" x2="11.5" y2="9"/><polyline points="6.5,10 5,9 6.5,8" strokeWidth="1.2"/><polyline points="11.5,10 13,9 11.5,8" strokeWidth="1.2"/></S>;
const IcUpbitFibTz = () => <S strokeWidth="1.4"><line x1="4" y1="4" x2="4" y2="16"/><line x1="8" y1="6" x2="8" y2="16" strokeOpacity="0.75"/><line x1="12" y1="3" x2="12" y2="16" strokeOpacity="0.9"/><line x1="15" y1="8" x2="15" y2="16" strokeOpacity="0.55"/><circle cx="4" cy="10" r="1.3" fill="currentColor" stroke="none"/><circle cx="8" cy="11" r="1.3" fill="currentColor" stroke="none"/><circle cx="12" cy="9" r="1.3" fill="currentColor" stroke="none"/><circle cx="15" cy="12" r="1.3" fill="currentColor" stroke="none"/></S>;
const IcFavorite = () => <S strokeWidth="1.3"><path d="M9 2.5 L11 7 L16 7.5 L12.5 11 L13.5 16 L9 13.5 L4.5 16 L5.5 11 L2 7.5 L7 7 Z" fill="currentColor" fillOpacity="0.25"/></S>;
// 선 그룹
const IcTrendline = () => <S strokeWidth="1.5"><line x1="3" y1="15" x2="15" y2="3"/><circle cx="3" cy="15" r="1.5" fill="currentColor" stroke="none"/><circle cx="15" cy="3" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcRay = () => <S strokeWidth="1.4"><line x1="3" y1="14" x2="16" y2="5"/><circle cx="3" cy="14" r="1.5" fill="currentColor" stroke="none"/><polyline points="14,5.5 16,5 15.5,7" strokeWidth="1.3"/></S>;
const IcInfoLine = () => <S strokeWidth="1.4"><line x1="1" y1="9" x2="17" y2="9"/><rect x="5" y="4" width="8" height="4" rx="1" strokeWidth="1.2"/><text x="9" y="7.5" fontSize="4.5" fill="currentColor" stroke="none" textAnchor="middle">i</text></S>;
const IcExtLine = () => <S strokeWidth="1.5"><line x1="1" y1="15" x2="17" y2="3"/><polyline points="15,3 17,3 17,5" strokeWidth="1.3"/><polyline points="3,15 1,15 1,13" strokeWidth="1.3"/></S>;
const IcTrendAngle = () => <S strokeWidth="1.4"><line x1="3" y1="15" x2="15" y2="7"/><path d="M3 15 Q8 15 10 11" strokeDasharray="2 1.5" strokeOpacity="0.7"/><text x="9" y="15" fontSize="6" fill="currentColor" stroke="none">θ</text></S>;
const IcHline = () => <S strokeWidth="1.4"><line x1="1" y1="9" x2="17" y2="9"/><line x1="1" y1="5" x2="17" y2="5" strokeOpacity="0.4" strokeDasharray="2 2"/><line x1="1" y1="13" x2="17" y2="13" strokeOpacity="0.4" strokeDasharray="2 2"/></S>;
const IcHlineRay = () => <S strokeWidth="1.4"><line x1="3" y1="9" x2="17" y2="9"/><circle cx="3" cy="9" r="1.5" fill="currentColor" stroke="none"/><polyline points="15,7 17,9 15,11" strokeWidth="1.3"/></S>;
const IcVline = () => <S strokeWidth="1.4"><line x1="9" y1="1" x2="9" y2="17"/><polyline points="7,4 9,1 11,4"/><polyline points="7,14 9,17 11,14"/></S>;
const IcCrossLine = () => <S strokeWidth="1.4"><line x1="2" y1="9" x2="16" y2="9"/><line x1="9" y1="2" x2="9" y2="16"/><circle cx="9" cy="9" r="1.8" fill="currentColor" stroke="none" fillOpacity="0.7"/></S>;
const IcChannel = () => <S strokeWidth="1.4"><line x1="2" y1="13" x2="16" y2="5"/><line x1="2" y1="16" x2="16" y2="8" strokeOpacity="0.5" strokeDasharray="3 2"/></S>;
const IcParaCh = () => <S strokeWidth="1.4"><line x1="2" y1="12" x2="16" y2="5"/><line x1="2" y1="15" x2="16" y2="8"/></S>;
const IcDisjCh = () => <S strokeWidth="1.3"><line x1="2" y1="13" x2="9" y2="8"/><line x1="2" y1="15" x2="9" y2="10" strokeOpacity="0.6"/><line x1="10" y1="10" x2="16" y2="6"/><line x1="10" y1="13" x2="16" y2="9" strokeOpacity="0.6"/></S>;
const IcFlatTop = () => <S strokeWidth="1.4"><line x1="2" y1="5" x2="16" y2="5"/><path d="M2 5 L4 9 L7 5 L10 11 L13 5 L16 5" strokeOpacity="0.7"/><line x1="2" y1="14" x2="16" y2="14" strokeOpacity="0.5"/></S>;
// 피보나치 그룹
const IcFib = () => <S strokeWidth="1.3"><line x1="2" y1="3" x2="16" y2="3"/><line x1="2" y1="7" x2="16" y2="7" strokeOpacity="0.6"/><line x1="2" y1="9" x2="16" y2="9"/><line x1="2" y1="12" x2="16" y2="12" strokeOpacity="0.6"/><line x1="2" y1="15" x2="16" y2="15"/><circle cx="2" cy="3" r="1.5" fill="currentColor" stroke="none"/><circle cx="2" cy="15" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcFibExt = () => <S strokeWidth="1.3"><line x1="2" y1="6" x2="16" y2="6" strokeOpacity="0.5"/><line x1="2" y1="9" x2="16" y2="9"/><line x1="2" y1="12" x2="16" y2="12" strokeOpacity="0.5"/><line x1="2" y1="15" x2="16" y2="15"/><line x1="2" y1="3" x2="16" y2="3" strokeOpacity="0.3" strokeDasharray="2 2"/><circle cx="2" cy="9" r="1.5" fill="currentColor" stroke="none"/><circle cx="2" cy="15" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcFibCh = () => <S strokeWidth="1.3"><line x1="2" y1="14" x2="16" y2="5"/><line x1="2" y1="11" x2="16" y2="2" strokeOpacity="0.6"/><line x1="2" y1="17" x2="16" y2="8" strokeOpacity="0.4"/></S>;
const IcFibTZ = () => <S strokeWidth="1.3"><line x1="2" y1="2" x2="2" y2="16"/><line x1="5" y1="2" x2="5" y2="16" strokeOpacity="0.7"/><line x1="8" y1="2" x2="8" y2="16" strokeOpacity="0.5"/><line x1="11" y1="2" x2="11" y2="16" strokeOpacity="0.7"/><line x1="15" y1="2" x2="15" y2="16" strokeOpacity="0.4"/></S>;
const IcFibFan = () => <S strokeWidth="1.3"><line x1="2" y1="16" x2="16" y2="3"/><line x1="2" y1="16" x2="16" y2="7" strokeOpacity="0.7"/><line x1="2" y1="16" x2="16" y2="11" strokeOpacity="0.5"/><line x1="2" y1="16" x2="16" y2="14" strokeOpacity="0.3"/></S>;
const IcFibTrendT = () => <S strokeWidth="1.3"><line x1="3" y1="14" x2="15" y2="6"/><line x1="5" y1="6" x2="5" y2="14" strokeOpacity="0.6"/><line x1="8" y1="6" x2="8" y2="14" strokeOpacity="0.7"/><line x1="11" y1="6" x2="11" y2="14" strokeOpacity="0.5"/></S>;
const IcFibCircles = () => <S strokeWidth="1.3"><circle cx="9" cy="9" r="2"/><circle cx="9" cy="9" r="5"/><circle cx="9" cy="9" r="7.5" strokeOpacity="0.4"/></S>;
const IcFibSpiral = () => <S strokeWidth="1.3"><path d="M9 9 Q12 6 13 9 Q14 13 9 14 Q4 14 4 9 Q4 4 9 3 Q15 3 16 9" strokeOpacity="0.8"/></S>;
const IcFibArc = () => <S strokeWidth="1.3"><line x1="2" y1="14" x2="16" y2="14" strokeOpacity="0.5"/><path d="M2 14 Q9 5 16 14" strokeOpacity="0.9"/><path d="M2 14 Q9 2 16 14" strokeOpacity="0.6"/></S>;
const IcFibWedge = () => <S strokeWidth="1.3"><path d="M2 15 L16 9 L16 15 Z"/><path d="M2 15 L16 6" strokeOpacity="0.5"/></S>;
const IcPitchfork = () => <S strokeWidth="1.4"><line x1="3" y1="9" x2="10" y2="7"/><line x1="10" y1="7" x2="16" y2="4"/><line x1="10" y1="7" x2="16" y2="10"/><circle cx="3" cy="9" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcPitchSchiff = () => <S strokeWidth="1.4"><line x1="3" y1="10" x2="9" y2="7"/><line x1="9" y1="7" x2="16" y2="3" strokeDasharray="3 2"/><line x1="9" y1="7" x2="16" y2="11"/></S>;
const IcPitchInside = () => <S strokeWidth="1.4"><line x1="3" y1="9" x2="11" y2="7"/><line x1="11" y1="7" x2="16" y2="5" strokeOpacity="0.6"/><line x1="11" y1="7" x2="16" y2="9" strokeOpacity="0.6"/></S>;
const IcGannBox = () => <S strokeWidth="1.3"><rect x="3" y="4" width="12" height="10" rx="0.5"/><line x1="3" y1="4" x2="15" y2="14" strokeOpacity="0.6"/><line x1="15" y1="4" x2="3" y2="14" strokeOpacity="0.4"/></S>;
const IcGannSq = () => <S strokeWidth="1.3"><rect x="4" y="4" width="10" height="10" rx="0.5"/><line x1="4" y1="4" x2="14" y2="14" strokeOpacity="0.6"/><line x1="14" y1="4" x2="4" y2="14" strokeOpacity="0.6"/></S>;
const IcGannFan = () => <S strokeWidth="1.3"><line x1="2" y1="16" x2="16" y2="2"/><line x1="2" y1="16" x2="16" y2="4" strokeOpacity="0.6"/><line x1="2" y1="16" x2="16" y2="8" strokeOpacity="0.5"/><line x1="2" y1="16" x2="16" y2="13" strokeOpacity="0.4"/></S>;
// 패턴 그룹
const IcXABCD = () => <S strokeWidth="1.4"><path d="M2 15 L6 5 L10 13 L13 7 L16 11"/><circle cx="2" cy="15" r="1.2" fill="currentColor" stroke="none"/><circle cx="6" cy="5" r="1.2" fill="currentColor" stroke="none"/><circle cx="10" cy="13" r="1.2" fill="currentColor" stroke="none"/><circle cx="13" cy="7" r="1.2" fill="currentColor" stroke="none"/><circle cx="16" cy="11" r="1.2" fill="currentColor" stroke="none"/></S>;
const IcCypher = () => <S strokeWidth="1.4"><path d="M2 14 L7 4 L11 12 L14 6 L16 10"/><circle cx="2" cy="14" r="1.2" fill="currentColor" stroke="none"/><circle cx="7" cy="4" r="1.2" fill="currentColor" stroke="none"/><circle cx="11" cy="12" r="1.2" fill="currentColor" stroke="none"/><circle cx="14" cy="6" r="1.2" fill="currentColor" stroke="none"/><circle cx="16" cy="10" r="1.2" fill="currentColor" stroke="none"/></S>;
const IcABCD = () => <S strokeWidth="1.4"><path d="M3 14 L8 4 L13 12 L16 6"/><circle cx="3" cy="14" r="1.2" fill="currentColor" stroke="none"/><circle cx="8" cy="4" r="1.2" fill="currentColor" stroke="none"/><circle cx="13" cy="12" r="1.2" fill="currentColor" stroke="none"/><circle cx="16" cy="6" r="1.2" fill="currentColor" stroke="none"/></S>;
const IcHeadShould = () => <S strokeWidth="1.4"><path d="M2 13 L5 8 L7 11 L9 4 L11 11 L13 8 L16 13"/></S>;
const IcWedgePat = () => <S strokeWidth="1.4"><path d="M2 5 L16 7"/><path d="M2 14 L16 11"/><path d="M5 5.4 L5 13.2" strokeOpacity="0.4" strokeDasharray="2 2"/><path d="M9 5.9 L9 12.5" strokeOpacity="0.4" strokeDasharray="2 2"/><path d="M13 6.4 L13 11.8" strokeOpacity="0.4" strokeDasharray="2 2"/></S>;
const IcThreeDrives = () => <S strokeWidth="1.4"><path d="M2 13 L5 8 L8 11 L11 6 L14 9 L16 6"/><circle cx="5" cy="8" r="1.2" fill="currentColor" stroke="none"/><circle cx="11" cy="6" r="1.2" fill="currentColor" stroke="none"/><circle cx="16" cy="6" r="1.2" fill="currentColor" stroke="none"/></S>;
const IcElliottImp = () => <S strokeWidth="1.3"><path d="M2 14 L5 6 L8 12 L11 4 L14 10 L16 8"/><text x="3" y="17" fontSize="4" fill="currentColor" stroke="none">1 2 3 4 5</text></S>;
const IcElliottCorr = () => <S strokeWidth="1.3"><path d="M2 6 L7 14 L12 8 L16 14"/><text x="3" y="4" fontSize="4.5" fill="currentColor" stroke="none">A B C</text></S>;
const IcElliottTri = () => <S strokeWidth="1.3"><path d="M2 8 L5 14 L8 9 L11 14 L14 10 L16 14"/><text x="2" y="4" fontSize="3.5" fill="currentColor" stroke="none">ABCDE</text></S>;
const IcElliottDbl = () => <S strokeWidth="1.3"><path d="M2 14 L5 8 L8 13 L11 7 L14 12 L16 10"/><text x="2" y="4" fontSize="4" fill="currentColor" stroke="none">W X Y</text></S>;
// 프로젝션 그룹
const IcLongPos = () => <S strokeWidth="1.3"><rect x="3" y="5" width="12" height="5" rx="0.5" fill="rgba(76,175,80,0.3)" stroke="#4CAF50"/><rect x="3" y="10" width="12" height="4" rx="0.5" fill="rgba(239,83,80,0.2)" stroke="#EF5350" strokeDasharray="2 1.5"/><line x1="3" y1="10" x2="15" y2="10" stroke="#9E9E9E"/></S>;
const IcShortPos = () => <S strokeWidth="1.3"><rect x="3" y="9" width="12" height="5" rx="0.5" fill="rgba(239,83,80,0.3)" stroke="#EF5350"/><rect x="3" y="5" width="12" height="4" rx="0.5" fill="rgba(76,175,80,0.2)" stroke="#4CAF50" strokeDasharray="2 1.5"/><line x1="3" y1="9" x2="15" y2="9" stroke="#9E9E9E"/></S>;
const IcForecast = () => <S strokeWidth="1.3"><line x1="2" y1="14" x2="9" y2="8"/><line x1="9" y1="8" x2="16" y2="5" strokeDasharray="3 2" strokeOpacity="0.7"/><circle cx="9" cy="8" r="2" fill="currentColor" stroke="none" fillOpacity="0.5"/></S>;
const IcBarPattern = () => <S strokeWidth="1.3"><rect x="2" y="3" width="6" height="12" rx="0.5" strokeOpacity="0.6"/><rect x="2" y="5" width="6" height="8" rx="0.5" fill="currentColor" fillOpacity="0.2"/><line x1="5" y1="3" x2="5" y2="5"/><line x1="5" y1="13" x2="5" y2="15"/><line x1="10" y1="3" x2="16" y2="6" strokeDasharray="2 1.5" strokeOpacity="0.6"/><line x1="10" y1="13" x2="16" y2="14" strokeDasharray="2 1.5" strokeOpacity="0.6"/></S>;
const IcGhostFeed = () => <S strokeWidth="1.3"><rect x="2" y="5" width="6" height="8" rx="0.5" strokeOpacity="0.4" strokeDasharray="2 1.5"/><rect x="10" y="4" width="6" height="10" rx="0.5" strokeOpacity="0.4" strokeDasharray="2 1.5"/><line x1="5" y1="5" x2="5" y2="3" strokeOpacity="0.4"/><line x1="13" y1="4" x2="13" y2="2" strokeOpacity="0.4"/></S>;
const IcProjection = () => <S strokeWidth="1.3"><path d="M2 13 L8 8"/><path d="M8 8 Q12 5 16 7" strokeDasharray="3 2" strokeOpacity="0.7"/><circle cx="8" cy="8" r="2" fill="currentColor" stroke="none" fillOpacity="0.5"/><circle cx="2" cy="13" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcVWAP = () => <S strokeWidth="1.3"><path d="M2 9 Q6 6 9 9 Q12 12 16 9" strokeOpacity="0.8"/><circle cx="2" cy="9" r="2" fill="currentColor" stroke="none" fillOpacity="0.7"/><line x1="2" y1="6" x2="2" y2="12" strokeOpacity="0.5"/></S>;
const IcVolProfile = () => <S strokeWidth="1.3"><line x1="3" y1="2" x2="3" y2="16"/><line x1="3" y1="5" x2="9" y2="5" strokeWidth="2.5" strokeOpacity="0.5"/><line x1="3" y1="8" x2="14" y2="8" strokeWidth="2.5" strokeOpacity="0.7"/><line x1="3" y1="11" x2="11" y2="11" strokeWidth="2.5" strokeOpacity="0.5"/><line x1="3" y1="14" x2="7" y2="14" strokeWidth="2.5" strokeOpacity="0.4"/></S>;
const IcPriceRange = () => <S strokeWidth="1.4"><line x1="2" y1="9" x2="16" y2="9"/><line x1="2" y1="6" x2="2" y2="12"/><line x1="16" y1="6" x2="16" y2="12"/></S>;
// 도형 그룹
const IcRect = () => <S strokeWidth="1.4"><rect x="3" y="5" width="12" height="8" rx="0.5"/></S>;
const IcRotRect = () => <S strokeWidth="1.4"><path d="M9 2 L16 7 L13 16 L3 14 L2 7 Z"/></S>;
const IcCircle = () => <S strokeWidth="1.4"><circle cx="9" cy="9" r="6.5"/></S>;
const IcEllipse = () => <S strokeWidth="1.4"><ellipse cx="9" cy="9" rx="7" ry="4.5"/></S>;
const IcTriangle = () => <S strokeWidth="1.4"><path d="M9 3 L16 15 L2 15 Z"/></S>;
const IcParallelo = () => <S strokeWidth="1.4"><path d="M4 14 L7 4 L16 4 L13 14 Z"/></S>;
const IcPolyline = () => <S strokeWidth="1.4"><path d="M2 14 L6 6 L10 11 L14 5"/><circle cx="2" cy="14" r="1.5" fill="currentColor" stroke="none"/><circle cx="14" cy="5" r="1.5" fill="currentColor" stroke="none"/></S>;
const IcArc = () => <S strokeWidth="1.4"><path d="M2 15 Q9 1 16 15"/></S>;
// 브러시 그룹
const IcPencil = () => <S strokeWidth="1.4"><path d="M3 15L3.5 11L13 2.5Q14.5 1 16 2.5Q17.5 4 16 5.5L6.5 15Z"/><line x1="11" y1="4" x2="14" y2="7"/></S>;
const IcHighlight = () => <S><path d="M3 13 Q9 8 15 13" strokeOpacity="0.5" strokeWidth="5" stroke="#FFEB3B" fill="none"/><path d="M3 13 Q9 8 15 13" strokeWidth="1.4" stroke="currentColor" fill="none"/></S>;
const IcArrowMark = () => <S strokeWidth="1.4"><line x1="3" y1="14" x2="15" y2="5"/><circle cx="3" cy="14" r="1.5" fill="currentColor" stroke="none"/><polyline points="11,4 15,5 14,9" strokeWidth="1.3" fill="currentColor" fillOpacity="0.5"/></S>;
const IcArrowAnch = () => <S strokeWidth="1.4"><circle cx="4" cy="14" r="2" fill="currentColor" stroke="none" fillOpacity="0.5"/><line x1="4" y1="12" x2="14" y2="5" strokeDasharray="2 1.5"/><polyline points="11,4 14,5 13,8" fill="currentColor" fillOpacity="0.6"/></S>;
const IcArrowUp = () => <S strokeWidth="1.4"><path d="M9 14 L9 7"/><path d="M5 10 L9 6 L13 10" fill="currentColor" fillOpacity="0.5"/></S>;
const IcArrowDown = () => <S strokeWidth="1.4"><path d="M9 4 L9 11"/><path d="M5 8 L9 12 L13 8" fill="currentColor" fillOpacity="0.5"/></S>;
const IcArrowLeft = () => <S strokeWidth="1.4"><path d="M14 9 L7 9"/><path d="M10 5 L6 9 L10 13" fill="currentColor" fillOpacity="0.5"/></S>;
const IcArrowRight = () => <S strokeWidth="1.4"><path d="M4 9 L11 9"/><path d="M8 5 L12 9 L8 13" fill="currentColor" fillOpacity="0.5"/></S>;
// 텍스트 그룹
const IcText = () => <svg width="18" height="18" viewBox="0 0 18 18" fill="none"><text x="3" y="14" fontSize="13" fontWeight="700" fill="currentColor" fontFamily="sans-serif">T</text></svg>;
const IcFixedText = () => <svg width="18" height="18" viewBox="0 0 18 18" fill="none"><text x="3" y="14" fontSize="13" fontWeight="700" fill="currentColor" fontFamily="sans-serif">T</text><circle cx="15" cy="15" r="3" fill="currentColor" fillOpacity="0.5" stroke="none"/></svg>;
const IcNote = () => <S strokeWidth="1.3"><rect x="2" y="3" width="14" height="12" rx="1.5"/><line x1="5" y1="7" x2="13" y2="7" strokeOpacity="0.7"/><line x1="5" y1="9.5" x2="13" y2="9.5" strokeOpacity="0.7"/><line x1="5" y1="12" x2="10" y2="12" strokeOpacity="0.7"/><circle cx="16" cy="3" r="2.5" stroke="none" fill="currentColor" fillOpacity="0.5"/></S>;
const IcPriceNote = () => <S strokeWidth="1.3"><line x1="2" y1="9" x2="8" y2="9"/><rect x="8" y="5" width="8" height="8" rx="1"/><text x="12" y="10.5" fontSize="5" fill="currentColor" stroke="none" textAnchor="middle">$</text></S>;
const IcPin = () => <S strokeWidth="1.3"><path d="M9 3 Q12 3 12 7 Q12 10 9 15 Q6 10 6 7 Q6 3 9 3 Z"/><circle cx="9" cy="7" r="1.8" stroke="none" fill="currentColor" fillOpacity="0.6"/></S>;
const IcTable = () => <S strokeWidth="1.3"><rect x="2" y="3" width="14" height="12" rx="1"/><line x1="2" y1="7" x2="16" y2="7"/><line x1="2" y1="11" x2="16" y2="11"/><line x1="7" y1="3" x2="7" y2="15"/><line x1="12" y1="3" x2="12" y2="15"/></S>;
const IcCallout = () => <S strokeWidth="1.3"><rect x="2" y="3" width="12" height="8" rx="1.5"/><path d="M7 11 L5 15 L10 11"/></S>;
const IcComment = () => <S strokeWidth="1.3"><path d="M2 3 Q2 2 3 2 L15 2 Q16 2 16 3 L16 11 Q16 12 15 12 L8 12 L5 15 L5 12 L3 12 Q2 12 2 11 Z"/></S>;
const IcPriceLabel = () => <S strokeWidth="1.3"><path d="M2 9 L6 9 L8 6 L12 6 L14 9 L12 12 L8 12 L6 9"/></S>;
const IcGuide = () => <S strokeWidth="1.3"><line x1="1" y1="9" x2="17" y2="9" strokeDasharray="3 2" strokeOpacity="0.8"/><circle cx="9" cy="9" r="3" strokeOpacity="0.6"/></S>;
const IcFlag = () => <S strokeWidth="1.3"><line x1="5" y1="3" x2="5" y2="15"/><path d="M5 3 L15 6 L5 9 Z" fill="currentColor" fillOpacity="0.4"/></S>;
// 측정 그룹
const IcMeasure = () => <S strokeWidth="1.4"><line x1="2" y1="9" x2="16" y2="9"/><line x1="2" y1="6" x2="2" y2="12"/><line x1="16" y1="6" x2="16" y2="12"/></S>;
const IcDateRange = () => <S strokeWidth="1.3"><rect x="2" y="4" width="14" height="12" rx="1"/><line x1="2" y1="8" x2="16" y2="8"/><line x1="6" y1="2" x2="6" y2="6"/><line x1="12" y1="2" x2="12" y2="6"/></S>;
const IcDatePriceR = () => <S strokeWidth="1.3"><rect x="2" y="3" width="14" height="12" rx="1" strokeOpacity="0.7"/><line x1="2" y1="9" x2="16" y2="9" strokeOpacity="0.6"/><line x1="9" y1="3" x2="9" y2="15" strokeOpacity="0.6"/></S>;
const IcDataWindow = () => <S strokeWidth="1.3"><rect x="2" y="3" width="14" height="12" rx="1"/><line x1="2" y1="7" x2="16" y2="7"/><line x1="7" y1="3" x2="7" y2="7"/></S>;
const IcZoom = () => <S strokeWidth="1.4"><circle cx="8" cy="8" r="5"/><line x1="12" y1="12" x2="16" y2="16"/><line x1="6" y1="8" x2="10" y2="8"/><line x1="8" y1="6" x2="8" y2="10"/></S>;
const IcMagnifier = () => <S strokeWidth="1.4"><circle cx="8" cy="8" r="5"/><line x1="12" y1="12" x2="16" y2="16"/></S>;
// 하단 버튼 아이콘
const IcMagnetWeak = () => <S strokeWidth="1.4"><path d="M5 14 L5 8 Q5 3 9 3 Q13 3 13 8 L13 14"/><line x1="3" y1="14" x2="7" y2="14" strokeWidth="2"/><line x1="11" y1="14" x2="15" y2="14" strokeWidth="2"/></S>;
const IcMagnetStrg = () => <S strokeWidth="1.4"><path d="M5 14 L5 8 Q5 3 9 3 Q13 3 13 8 L13 14"/><line x1="3" y1="14" x2="7" y2="14" strokeWidth="2"/><line x1="11" y1="14" x2="15" y2="14" strokeWidth="2"/><line x1="9" y1="9" x2="9" y2="13" strokeDasharray="1.5 1" strokeOpacity="0.7"/></S>;
const IcLock = () => <S strokeWidth="1.4"><rect x="4" y="9" width="10" height="7" rx="1"/><path d="M6 9V6Q6 3 9 3Q12 3 12 6V9"/></S>;
const IcEye = () => <S strokeWidth="1.4"><path d="M1 9Q5 3 9 3Q13 3 17 9Q13 15 9 15Q5 15 1 9Z"/><circle cx="9" cy="9" r="2.5"/></S>;
const IcEyeOff = () => <S strokeWidth="1.4"><path d="M1 9Q5 3 9 3Q13 3 17 9Q13 15 9 15Q5 15 1 9Z" strokeOpacity="0.3"/><line x1="3" y1="3" x2="15" y2="15"/></S>;
const IcTrash = () => <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"><line x1="3" y1="5" x2="13" y2="5"/><path d="M6 5V3Q6 2 8 2Q10 2 10 3V5"/><path d="M4 5L4.5 14Q4.5 14.5 5 14.5L11 14.5Q11.5 14.5 11.5 14L12 5"/></svg>;
const IcUndo = () => <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M3 7C3 4 5.5 2 8.5 2C11.5 2 14 4 14 7C14 10 11.5 12 8.5 12"/><polyline points="1,5 3,7 5,5"/></svg>;
const IcLayers = () => <S strokeWidth="1.4"><path d="M2 12 L9 15 L16 12"/><path d="M2 9 L9 12 L16 9"/><path d="M2 6 L9 9 L16 6"/></S>;
// ─── 타입 ──────────────────────────────────────────────────────────────────
type FlyoutItem =
| { kind: 'tool'; id: string; title: string; shortcut?: string; icon: React.ReactNode }
| { kind: 'sep'; label?: string };
interface GroupDef {
id: number;
label: string;
defaultIcon: React.ReactNode;
items: FlyoutItem[];
}
// ─── 그룹 정의 ──────────────────────────────────────────────────────────────
const T = (id: string, title: string, icon: React.ReactNode, shortcut?: string): FlyoutItem =>
({ kind: 'tool', id, title, icon, shortcut });
const SEP = (label?: string): FlyoutItem => ({ kind: 'sep', label });
/** 업비트 차트 좌측 즐겨찾기 플로팅 툴바와 동일 4종 */
export const FAVORITE_DRAWING_TOOLS = ['hline', 'vline', 'date_range', 'fibtz'] as const;
const GROUPS: GroupDef[] = [
{
id: 0, label: '즐겨찾기', defaultIcon: <IcFavorite />,
items: [
T('hline', '가로줄', <IcUpbitHline />, '⌥H'),
T('vline', '세로줄', <IcUpbitVline />, '⌥V'),
T('date_range', '기간', <IcUpbitPeriod />),
T('fibtz', '피보나치 타임존', <IcUpbitFibTz />),
],
},
{
id: 1, label: '라인', defaultIcon: <IcTrendline />,
items: [
T('trendline', '추세줄', <IcTrendline />, '⌥T'),
T('ray', '빛', <IcRay />),
T('info_line', '인포 라인', <IcInfoLine />),
T('extended_line', '익스텐디드 라인', <IcExtLine />),
T('trend_angle', '추세각', <IcTrendAngle />),
SEP(),
T('hline', '가로줄', <IcHline />, '⌥H'),
T('hline_ray', '가로빛', <IcHlineRay />, '⌥J'),
T('vline', '수직선', <IcVline />, '⌥V'),
T('cross_line', '크로스 라인', <IcCrossLine />, '⌥C'),
SEP('채널'),
T('channel', '추세 채널', <IcChannel />),
T('parallel_channel','패러렐 채널', <IcParaCh />),
T('disjoint_channel','디스조인트 채널',<IcDisjCh />),
T('flat_top_bottom','플랫 탑/바텀', <IcFlatTop />),
],
},
{
id: 2, label: '피보나치', defaultIcon: <IcFib />,
items: [
T('fib', '피보나치 되돌림', <IcFib />, '⌥F'),
T('fib_ext', '추세기반 피보나치 확장', <IcFibExt />),
T('fib_channel', '피보나치 채널', <IcFibCh />),
T('fibtz', '피보나치 타임존', <IcFibTZ />),
T('fib_speed_fan', '스피드 리지스턴스 팬', <IcFibFan />),
T('fib_trend_time','추세기반 피보나치 시간', <IcFibTrendT />),
T('fib_circles', '피보나치 서클', <IcFibCircles />),
T('fib_spiral', '피보나치 스파이럴', <IcFibSpiral />),
T('fib_speed_arc', '스피드 리지스턴스 아크', <IcFibArc />),
T('fib_wedge', '피보나치 웻지', <IcFibWedge />),
SEP('피치포크'),
T('pitchfork', '앤드류스 피치포크', <IcPitchfork />),
T('pitchfork_schiff', '쉬프 피치포크', <IcPitchSchiff />),
T('pitchfork_inside', '인사이드 피치포크', <IcPitchInside />),
SEP('갠'),
T('gann_box', '갠 박스', <IcGannBox />),
T('gann_square', '갠 스퀘어', <IcGannSq />),
T('gann_fan', '갠 팬', <IcGannFan />),
],
},
{
id: 3, label: '패턴', defaultIcon: <IcXABCD />,
items: [
T('xabcd', 'XABCD 패턴', <IcXABCD />),
T('cypher', '사이퍼 패턴', <IcCypher />),
T('head_shoulders', '헤드 앤 숄더', <IcHeadShould />),
T('abcd', 'ABCD 패턴', <IcABCD />),
T('wedge_pattern', '세모 패턴', <IcWedgePat />),
T('three_drives', '쓰리 드라이브 패턴', <IcThreeDrives />),
SEP('엘리엇 웨이브'),
T('elliott_impulse', '엘리엇 임펄스 파동 (12345)', <IcElliottImp />),
T('elliott_correction', '엘리엇 코렉션 파동 (ABC)', <IcElliottCorr />),
T('elliott_triangle_wave', '엘리엇 트라이앵글 웨이브 (ABCDE)',<IcElliottTri />),
T('elliott_double_combo', '엘리엇 다블콤보 파동 (WXY)', <IcElliottDbl />),
],
},
{
id: 4, label: '프로젝션', defaultIcon: <IcLongPos />,
items: [
T('long_position', '매수 포지션', <IcLongPos />),
T('short_position', '숏 포지션', <IcShortPos />),
T('forecast', '예측', <IcForecast />),
T('bar_pattern', '봉패턴', <IcBarPattern />),
T('ghost_feed', '고스트피드', <IcGhostFeed />),
T('projection_tool','프로젝션', <IcProjection />),
SEP('볼륨-기반'),
T('anchored_vwap', '앵커드 VWAP', <IcVWAP />),
T('fixed_volume_profile', '픽스트 레인지 볼륨 프로파일', <IcVolProfile />),
SEP('계측기'),
T('price_range', '가격범위', <IcPriceRange />),
T('date_range', '날짜 범위', <IcDateRange />),
T('date_price_range','날짜&가격 범위', <IcDatePriceR />),
],
},
{
id: 5, label: '도형', defaultIcon: <IcRect />,
items: [
T('rect', '네모', <IcRect />, '⌥⇧R'),
T('rotated_rect', '회전 사각형', <IcRotRect />),
T('circle', '원', <IcCircle />),
T('ellipse', '타원', <IcEllipse />),
T('triangle', '삼각형', <IcTriangle />),
T('parallelogram', '평행사변형', <IcParallelo />),
T('polyline', '다각형', <IcPolyline />),
T('arc', '호', <IcArc />),
],
},
{
id: 6, label: '브러시', defaultIcon: <IcPencil />,
items: [
T('pencil', '붓', <IcPencil />),
T('highlight', '하이라이터', <IcHighlight />),
SEP('화살'),
T('arrow_mark', '화살표', <IcArrowMark />),
T('anchored_arrow', '화살표', <IcArrowAnch />),
T('arrow_mark_up', '위화살표', <IcArrowUp />),
T('arrow_mark_down', '아래화살표', <IcArrowDown />),
T('arrow_mark_left', '왼화살표', <IcArrowLeft />),
T('arrow_mark_right', '오른화살표', <IcArrowRight />),
SEP('세이프'),
T('rect', '네모', <IcRect />, '⌥⇧R'),
T('circle', '원', <IcCircle />),
T('triangle', '삼각형', <IcTriangle />),
T('ellipse', '타원', <IcEllipse />),
],
},
{
id: 7, label: '텍스트 & 노트', defaultIcon: <IcText />,
items: [
T('text', '텍스트', <IcText />, 'T'),
T('fixed_text', '고정위치문자', <IcFixedText />),
T('note_text', '노트', <IcNote />),
T('price_note', '프라이스 노트', <IcPriceNote />),
T('pin_mark', '핀', <IcPin />),
T('table_tool', '테이블', <IcTable />),
T('callout', '콜아웃', <IcCallout />),
T('comment_mark', '코멘트', <IcComment />),
T('price_label', '가격라벨', <IcPriceLabel />),
T('guide_line', '길잡이', <IcGuide />),
T('flag_mark', '플래그 마크', <IcFlag />),
],
},
{
id: 8, label: '측정', defaultIcon: <IcMeasure />,
items: [
T('measure', '가격 측정', <IcMeasure />, 'M'),
T('date_range', '날짜 범위', <IcDateRange />),
T('date_price_range', '날짜&가격 범위', <IcDatePriceR />),
T('data_window', '데이터 창', <IcDataWindow />),
],
},
{
id: 9, label: '확대/축소', defaultIcon: <IcZoom />,
items: [
T('zoom', '확대/축소', <IcZoom />),
T('magnifier', '돋보기', <IcMagnifier />),
],
},
];
/** 차트 모드에서도 사용 가능한 확대 도구 (드로잉 툴바는 chart 모드에서 기본 비활성) */
export const CHART_ZOOM_TOOLS = new Set(['zoom', 'magnifier']);
// ─── Component ────────────────────────────────────────────────────────────────
const DrawingToolbar: React.FC<DrawingToolbarProps> = ({
activeTool, mode, onTool, onClearAll, onUndo,
locked, visible, onToggleLock, onToggleEye,
indicatorsVisible = true,
magnetMode = 'off',
snapToIndicator = false,
drawingCount = 0,
indicatorCount = 0,
onMagnetMode,
onSnapToIndicator,
onHideDrawings,
onHideIndicators,
onHideAll,
onClearIndicators,
keepLockedOnDelete = false,
onToggleKeepLocked,
}) => {
const disabled = mode === 'chart';
const canSelectTool = (toolId: string) => !disabled || CHART_ZOOM_TOOLS.has(toolId);
const [selectedPerGroup, setSelectedPerGroup] = useState<Record<number, string>>({});
const [openGroup, setOpenGroup] = useState<number | null>(null);
const [flyoutKind, setFlyoutKind] = useState<'group' | 'magnet' | 'eye' | 'trash' | null>(null);
const [flyoutPos, setFlyoutPos] = useState<{ top: number; left: number }>({ top: 0, left: 0 });
const openTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => () => {
if (openTimer.current) clearTimeout(openTimer.current);
if (closeTimer.current) clearTimeout(closeTimer.current);
}, []);
const getRepIcon = (group: GroupDef): React.ReactNode => {
const selId = selectedPerGroup[group.id];
if (selId) {
const it = group.items.find(i => i.kind === 'tool' && i.id === selId);
if (it && it.kind === 'tool') return it.icon;
}
const active = group.items.find(i => i.kind === 'tool' && i.id === activeTool);
if (active && active.kind === 'tool') return active.icon;
return group.defaultIcon;
};
const isGroupActive = (group: GroupDef) =>
group.items.some(i => i.kind === 'tool' && i.id === activeTool);
const openFlyout = (kind: 'group' | 'magnet' | 'eye' | 'trash', groupId: number | null, el: HTMLElement) => {
if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; }
if (openTimer.current) clearTimeout(openTimer.current);
openTimer.current = setTimeout(() => {
const rect = el.getBoundingClientRect();
setFlyoutPos({ top: rect.top, left: rect.right + 4 });
setOpenGroup(groupId);
setFlyoutKind(kind);
}, 80);
};
const scheduleClose = () => {
if (openTimer.current) { clearTimeout(openTimer.current); openTimer.current = null; }
closeTimer.current = setTimeout(() => { setFlyoutKind(null); setOpenGroup(null); }, 200);
};
const cancelClose = () => {
if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; }
};
const selectTool = (groupId: number, toolId: string) => {
if (!canSelectTool(toolId)) return;
setSelectedPerGroup(prev => ({ ...prev, [groupId]: toolId }));
setFlyoutKind(null); setOpenGroup(null);
if (openTimer.current) clearTimeout(openTimer.current);
if (closeTimer.current) clearTimeout(closeTimer.current);
onTool(toolId);
};
const handleGroupClick = (group: GroupDef) => {
if (disabled && group.id !== 9) return;
const selId = selectedPerGroup[group.id];
const toolId = (selId && group.items.find(i => i.kind === 'tool' && i.id === selId))
? selId
: (group.items.find(i => i.kind === 'tool' && i.id === activeTool) as { kind: 'tool'; id: string } | undefined)?.id
?? (group.items.find(i => i.kind === 'tool') as { kind: 'tool'; id: string } | undefined)?.id;
if (toolId && canSelectTool(toolId)) onTool(toolId);
};
const closeFlyout = () => { setFlyoutKind(null); setOpenGroup(null); };
// 현재 마그넷 아이콘
const magnetIcon = magnetMode === 'strong' ? <IcMagnetStrg /> : <IcMagnetWeak />;
const eyeIsOff = !visible && !indicatorsVisible;
return (
<div className={`tv-drawing-sidebar ${disabled ? 'chart-mode' : 'trading-mode'}`}>
{/* ── 커서 ── */}
<button
className={`tv-side-btn ${activeTool === 'cursor' && !disabled ? 'active' : ''}`}
title="커서 (Esc)"
onClick={() => !disabled && onTool('cursor')}
>
<IcCursor />
</button>
<div className="tv-side-sep" />
{/* ── 툴 그룹 (즐겨찾기 → 라인 …) ── */}
{GROUPS.map(group => (
<div key={group.id} className="tv-side-group">
<button
className={`tv-side-btn tv-side-group-btn${group.id === 0 ? ' tv-side-group-btn--favorite' : ''} ${isGroupActive(group) && (!disabled || group.id === 9) ? 'active' : ''}`}
onMouseEnter={e => openFlyout('group', group.id, e.currentTarget)}
onMouseLeave={scheduleClose}
onClick={() => handleGroupClick(group)}
title={group.label}
>
{getRepIcon(group)}
<span className="tv-side-expand-arrow" />
</button>
</div>
))}
<div className="tv-side-spacer" />
<div className="tv-side-sep" />
{/* ── 하단 버튼: 마그넷 ── */}
<button
className={`tv-side-btn ${magnetMode !== 'off' ? 'active' : ''}`}
title="마그넷"
onMouseEnter={e => openFlyout('magnet', null, e.currentTarget)}
onMouseLeave={scheduleClose}
>
{magnetIcon}
</button>
{/* ── 드로잉 잠금 ── */}
<button
className={`tv-side-btn ${locked ? 'active tv-side-lock-on' : ''}`}
title={locked ? '잠금 해제' : '드로잉 잠금'}
onClick={() => !disabled && onToggleLock()}
>
<IcLock />
</button>
{/* ── 표시/숨기기 토글 (플라이아웃) ── */}
<button
className={`tv-side-btn ${eyeIsOff ? 'tv-side-eye-off active' : (!visible || !indicatorsVisible) ? 'tv-side-eye-partial' : ''}`}
title="표시/숨기기"
onMouseEnter={e => openFlyout('eye', null, e.currentTarget)}
onMouseLeave={scheduleClose}
onClick={() => !disabled && onToggleEye()}
>
{eyeIsOff ? <IcEyeOff /> : <IcEye />}
</button>
<div className="tv-side-sep" />
{/* ── 실행 취소 ── */}
<button className="tv-side-btn" title="실행 취소 (Ctrl+Z)" onClick={() => !disabled && onUndo()}>
<IcUndo />
</button>
{/* ── 삭제 (플라이아웃) ── */}
<button
className="tv-side-btn tv-side-clear"
title="삭제"
onMouseEnter={e => openFlyout('trash', null, e.currentTarget)}
onMouseLeave={scheduleClose}
onClick={() => !disabled && onClearAll()}
>
<IcTrash />
</button>
{/* ── 레이어 ── */}
<button className="tv-side-btn" title="레이어">
<IcLayers />
</button>
{/* ════════════════════════════════════════════
── 플라이아웃 팝업 (fixed 위치)
════════════════════════════════════════════ */}
{flyoutKind !== null && (!disabled || (flyoutKind === 'group' && openGroup === 9)) && (
<div
className="tv-side-flyout"
style={{ top: flyoutPos.top, left: flyoutPos.left }}
onMouseEnter={cancelClose}
onMouseLeave={scheduleClose}
>
{/* ── 툴 그룹 플라이아웃 ── */}
{flyoutKind === 'group' && (() => {
const group = GROUPS.find(g => g.id === openGroup);
if (!group) return null;
return (
<>
<div className="tv-side-flyout-header">{group.label}</div>
{group.items.map((item, idx) =>
item.kind === 'sep' ? (
<div key={`sep-${idx}`} className="tv-side-flyout-sep">
{item.label && <span className="tv-side-flyout-sec-label">{item.label}</span>}
</div>
) : (
<button
key={item.id}
className={`tv-side-flyout-item ${activeTool === item.id ? 'active' : ''}${item.kind === 'tool' && !canSelectTool(item.id) ? ' disabled' : ''}`}
disabled={item.kind === 'tool' && !canSelectTool(item.id)}
onClick={() => item.kind === 'tool' && selectTool(group.id, item.id)}
>
<span className="tv-side-flyout-icon">{item.icon}</span>
<span className="tv-side-flyout-title">{item.title}</span>
{item.shortcut && <span className="tv-side-flyout-shortcut">{item.shortcut}</span>}
</button>
)
)}
</>
);
})()}
{/* ── 마그넷 플라이아웃 ── */}
{flyoutKind === 'magnet' && (
<>
<div className="tv-side-flyout-header">마그넷</div>
{(['weak','strong'] as const).map(m => (
<button
key={m}
className={`tv-side-flyout-item ${magnetMode === m ? 'active' : ''}`}
onClick={() => { onMagnetMode?.(m); closeFlyout(); }}
>
<span className="tv-side-flyout-icon">{m === 'weak' ? <IcMagnetWeak /> : <IcMagnetStrg />}</span>
<span className="tv-side-flyout-title">{m === 'weak' ? '위크 마그넷' : '스트롱 마그넷'}</span>
</button>
))}
{magnetMode !== 'off' && (
<button className="tv-side-flyout-item" onClick={() => { onMagnetMode?.('off'); closeFlyout(); }}>
<span className="tv-side-flyout-icon"><IcMagnetWeak /></span>
<span className="tv-side-flyout-title" style={{ opacity: 0.5 }}>마그넷 끄기</span>
</button>
)}
<div className="tv-side-flyout-sep" />
<div className="tv-side-flyout-toggle-row">
<span className="tv-side-flyout-title">인디케이터에 스냅</span>
<button
className={`tv-flyout-toggle ${snapToIndicator ? 'on' : ''}`}
onClick={() => onSnapToIndicator?.(!snapToIndicator)}
/>
</div>
</>
)}
{/* ── 눈 플라이아웃 ── */}
{flyoutKind === 'eye' && (
<>
<div className="tv-side-flyout-header">표시/숨기기</div>
<button className="tv-side-flyout-item" onClick={() => { onHideDrawings?.(); closeFlyout(); }}>
<span className="tv-side-flyout-icon"><IcEye /></span>
<span className="tv-side-flyout-title">드로잉 숨기기</span>
</button>
<button className="tv-side-flyout-item" onClick={() => { onHideIndicators?.(); closeFlyout(); }}>
<span className="tv-side-flyout-icon"><IcEye /></span>
<span className="tv-side-flyout-title">인디케이터 숨기기</span>
</button>
<button className="tv-side-flyout-item" onClick={() => { onHideAll?.(); closeFlyout(); }}>
<span className="tv-side-flyout-icon"><IcEyeOff /></span>
<span className="tv-side-flyout-title">모두 숨기기</span>
</button>
</>
)}
{/* ── 삭제 플라이아웃 ── */}
{flyoutKind === 'trash' && (
<>
<div className="tv-side-flyout-header">삭제</div>
<button
className="tv-side-flyout-item"
onClick={() => { onClearAll(); closeFlyout(); }}
>
<span className="tv-side-flyout-icon"><IcTrash /></span>
<span className="tv-side-flyout-title">{drawingCount} 드로잉 없애기</span>
</button>
<button
className="tv-side-flyout-item"
onClick={() => { onClearIndicators?.(); closeFlyout(); }}
>
<span className="tv-side-flyout-icon"><IcLayers /></span>
<span className="tv-side-flyout-title">{indicatorCount} 인디케이터 없애기</span>
</button>
<button
className="tv-side-flyout-item"
onClick={() => { onClearAll(); onClearIndicators?.(); closeFlyout(); }}
>
<span className="tv-side-flyout-icon"><IcTrash /></span>
<span className="tv-side-flyout-title">{drawingCount} 드로잉 &amp; {indicatorCount} 인디케이터 없애기</span>
</button>
<div className="tv-side-flyout-sep" />
<div className="tv-side-flyout-toggle-row">
<span className="tv-side-flyout-title">잠긴 드로잉은 항상 제거하세요</span>
<button
className={`tv-flyout-toggle ${keepLockedOnDelete ? 'on' : ''}`}
onClick={() => onToggleKeepLocked?.()}
/>
</div>
</>
)}
</div>
)}
</div>
);
};
export default DrawingToolbar;