goldenChat base source add
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { Drawing, FibTZSettings, FibTZLevel, FibTZBaseline, DrawingStyle } from '../types';
|
||||
import { DEFAULT_FIB_TZ_SETTINGS } from '../types';
|
||||
import { useDraggablePanel } from '../hooks/useDraggablePanel';
|
||||
|
||||
const LINE_STYLES: { value: DrawingStyle; label: string }[] = [
|
||||
{ value: 'solid', label: '━━' },
|
||||
{ value: 'dashed', label: '╌╌' },
|
||||
{ value: 'dotted', label: '···' },
|
||||
];
|
||||
const LINE_WIDTHS = [1, 2, 3, 4] as const;
|
||||
|
||||
// ─── Sub-components ──────────────────────────────────────────────────────────
|
||||
|
||||
function ColorCell({
|
||||
color,
|
||||
onChange,
|
||||
}: {
|
||||
color: string;
|
||||
onChange: (c: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<label
|
||||
title="색상 선택"
|
||||
style={{
|
||||
display: 'block',
|
||||
width: 28,
|
||||
height: 22,
|
||||
borderRadius: 3,
|
||||
background: color,
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
value={color}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
style={{ opacity: 0, position: 'absolute', inset: 0, width: '100%', height: '100%', cursor: 'pointer' }}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function StyleSelect({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: DrawingStyle;
|
||||
onChange: (s: DrawingStyle) => void;
|
||||
}) {
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value as DrawingStyle)}
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
border: '1px solid rgba(122,162,247,0.25)',
|
||||
borderRadius: 3,
|
||||
color: 'inherit',
|
||||
fontSize: 12,
|
||||
padding: '2px 4px',
|
||||
width: 54,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{LINE_STYLES.map(s => (
|
||||
<option key={s.value} value={s.value}>{s.label}</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
function WidthSelect({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
onChange: (w: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
onChange={e => onChange(Number(e.target.value))}
|
||||
style={{
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
border: '1px solid rgba(122,162,247,0.25)',
|
||||
borderRadius: 3,
|
||||
color: 'inherit',
|
||||
fontSize: 12,
|
||||
padding: '2px 4px',
|
||||
width: 44,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{LINE_WIDTHS.map(w => (
|
||||
<option key={w} value={w}>{w}</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Main component ───────────────────────────────────────────────────────────
|
||||
|
||||
interface Props {
|
||||
drawing: Drawing;
|
||||
onSave: (updated: Drawing) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const FibTZSettingsModal: React.FC<Props> = ({ drawing, onSave, onCancel }) => {
|
||||
const init = drawing.fibtzSettings ?? DEFAULT_FIB_TZ_SETTINGS;
|
||||
|
||||
const [baseline, setBaseline] = useState<FibTZBaseline>({ ...init.baseline });
|
||||
const [levels, setLevels] = useState<FibTZLevel[]>(
|
||||
init.levels.map(l => ({ ...l }))
|
||||
);
|
||||
|
||||
const updateBaseline = (patch: Partial<FibTZBaseline>) =>
|
||||
setBaseline(prev => ({ ...prev, ...patch }));
|
||||
|
||||
const updateLevel = (idx: number, patch: Partial<FibTZLevel>) =>
|
||||
setLevels(prev => prev.map((l, i) => i === idx ? { ...l, ...patch } : l));
|
||||
|
||||
const handleReset = () => {
|
||||
setBaseline({ ...DEFAULT_FIB_TZ_SETTINGS.baseline });
|
||||
setLevels(DEFAULT_FIB_TZ_SETTINGS.levels.map(l => ({ ...l })));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const settings: FibTZSettings = { baseline, levels };
|
||||
onSave({ ...drawing, fibtzSettings: settings });
|
||||
};
|
||||
|
||||
const cellStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
fontSize: 10,
|
||||
color: 'rgba(192,202,245,0.55)',
|
||||
textAlign: 'center',
|
||||
paddingBottom: 6,
|
||||
};
|
||||
|
||||
const {
|
||||
panelRef,
|
||||
dragging,
|
||||
onHeaderPointerDown, headerTouchStyle,
|
||||
panelStyle,
|
||||
headerCursor,
|
||||
} = useDraggablePanel({ centerOnMount: true });
|
||||
|
||||
return (
|
||||
<div
|
||||
className="dsm-overlay"
|
||||
style={{ zIndex: 1200 }}
|
||||
onMouseDown={e => { if (e.target === e.currentTarget) onCancel(); }}
|
||||
>
|
||||
<div
|
||||
ref={panelRef}
|
||||
className="dsm-dialog"
|
||||
style={{
|
||||
...panelStyle,
|
||||
width: 460,
|
||||
maxHeight: '90vh',
|
||||
cursor: dragging ? 'grabbing' : undefined,
|
||||
}}
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className="gc-popup-header"
|
||||
onPointerDown={onHeaderPointerDown}
|
||||
style={{ cursor: headerCursor, ...headerTouchStyle }}
|
||||
>
|
||||
<span className="gc-popup-title">피보나치 타임존 설정</span>
|
||||
<button type="button" className="gc-popup-close" onClick={onCancel} aria-label="닫기">✕</button>
|
||||
</div>
|
||||
<div className="dsm-body" style={{ color: 'var(--text, #c0caf5)' }}>
|
||||
|
||||
{/* ── 기준선 (0) ── */}
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div style={{ fontSize: 11, color: 'rgba(192,202,245,0.6)', marginBottom: 8, fontWeight: 600 }}>
|
||||
기준선 (항상 표시)
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '28px 28px 54px 44px',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
background: 'rgba(255,255,255,0.04)',
|
||||
borderRadius: 5,
|
||||
padding: '8px 10px',
|
||||
}}
|
||||
>
|
||||
{/* 항상 활성화 표시 */}
|
||||
<div style={{ ...cellStyle }}>
|
||||
<div style={{
|
||||
width: 14, height: 14, borderRadius: 3,
|
||||
background: 'var(--accent, #7aa2f7)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none">
|
||||
<polyline points="1.5,4.5 3.5,7 7.5,2" stroke="#fff" strokeWidth="1.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<ColorCell color={baseline.color} onChange={c => updateBaseline({ color: c })} />
|
||||
<StyleSelect value={baseline.style} onChange={s => updateBaseline({ style: s })} />
|
||||
<WidthSelect value={baseline.lineWidth} onChange={w => updateBaseline({ lineWidth: w })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 레벨 목록 ── */}
|
||||
<div>
|
||||
<div style={{ fontSize: 11, color: 'rgba(192,202,245,0.6)', marginBottom: 6, fontWeight: 600 }}>
|
||||
레벨
|
||||
</div>
|
||||
|
||||
{/* 헤더 행 */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '28px 60px 28px 54px 44px',
|
||||
gap: 8,
|
||||
padding: '0 10px',
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
<div style={headerStyle}>표시</div>
|
||||
<div style={headerStyle}>값</div>
|
||||
<div style={headerStyle}>색상</div>
|
||||
<div style={headerStyle}>스타일</div>
|
||||
<div style={headerStyle}>두께</div>
|
||||
</div>
|
||||
|
||||
{/* 레벨 행들 */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{levels.map((lv, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '28px 60px 28px 54px 44px',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
background: idx % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'transparent',
|
||||
borderRadius: 4,
|
||||
padding: '5px 10px',
|
||||
}}
|
||||
>
|
||||
{/* 표시 체크박스 */}
|
||||
<div style={cellStyle}>
|
||||
<label style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={lv.enabled}
|
||||
onChange={e => updateLevel(idx, { enabled: e.target.checked })}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
<div style={{
|
||||
width: 14, height: 14, borderRadius: 3,
|
||||
background: lv.enabled ? 'var(--accent, #7aa2f7)' : 'rgba(255,255,255,0.1)',
|
||||
border: lv.enabled ? 'none' : '1px solid rgba(255,255,255,0.25)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{lv.enabled && (
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none">
|
||||
<polyline points="1.5,4.5 3.5,7 7.5,2" stroke="#fff" strokeWidth="1.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 값 (편집 가능) */}
|
||||
<input
|
||||
type="number"
|
||||
value={lv.value}
|
||||
min={1}
|
||||
onChange={e => updateLevel(idx, { value: Math.max(1, Number(e.target.value)) })}
|
||||
style={{
|
||||
width: '100%',
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
border: '1px solid rgba(122,162,247,0.25)',
|
||||
borderRadius: 3,
|
||||
color: 'inherit',
|
||||
fontSize: 12,
|
||||
padding: '3px 5px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 색상 */}
|
||||
<ColorCell color={lv.color} onChange={c => updateLevel(idx, { color: c })} />
|
||||
|
||||
{/* 스타일 */}
|
||||
<StyleSelect value={lv.style} onChange={s => updateLevel(idx, { style: s })} />
|
||||
|
||||
{/* 두께 */}
|
||||
<WidthSelect value={lv.lineWidth} onChange={w => updateLevel(idx, { lineWidth: w })} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="dsm-footer" style={{ marginTop: 18 }}>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
style={{
|
||||
padding: '6px 14px',
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(122,162,247,0.3)',
|
||||
borderRadius: 5,
|
||||
cursor: 'pointer',
|
||||
color: 'inherit',
|
||||
fontSize: 13,
|
||||
}}
|
||||
>초기화</button>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
style={{
|
||||
padding: '6px 14px',
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(122,162,247,0.3)',
|
||||
borderRadius: 5,
|
||||
cursor: 'pointer',
|
||||
color: 'inherit',
|
||||
fontSize: 13,
|
||||
}}
|
||||
>취소</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
background: 'var(--accent, #7aa2f7)',
|
||||
border: 'none',
|
||||
borderRadius: 5,
|
||||
cursor: 'pointer',
|
||||
color: '#1a1b26',
|
||||
fontWeight: 600,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FibTZSettingsModal;
|
||||
Reference in New Issue
Block a user