goldenChat base source add
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 보조지표 설정 — 스타일 탭 (플롯·수평선 색상/굵기/타입)
|
||||
* 타이틀 열 너비를 섹션 내 최장 라벨에 맞춰 컨트롤 상하 정렬
|
||||
*/
|
||||
import React, { useLayoutEffect, useRef } from 'react';
|
||||
import type { PlotDef, HLineDef, HLineStyle } from '../utils/indicatorRegistry';
|
||||
import { getHLineLabel } from '../utils/indicatorRegistry';
|
||||
import { getPlotLabel } from '../utils/indicatorLabels';
|
||||
import { getHlinePriceSpec } from '../utils/indicatorParamSpec';
|
||||
import NumericParamInput from './NumericParamInput';
|
||||
import { ColorInput } from './IndicatorSettingsSections';
|
||||
import { LINE_WIDTH_OPTIONS, LINE_STYLE_OPTIONS } from '../utils/plotColorUtils';
|
||||
import { IchimokuCloudSection } from './IndicatorSettingsSections';
|
||||
import { getIchimokuPlotTitle, type IchimokuCloudColors } from '../utils/ichimokuConfig';
|
||||
|
||||
/** 스타일 탭 선 유형 드롭다운 (업비트 표기) */
|
||||
const LINE_STYLE_TAB_LABELS: Record<HLineStyle, string> = {
|
||||
solid: '라인',
|
||||
dashed: '점선',
|
||||
dotted: '도트',
|
||||
};
|
||||
|
||||
export interface IndicatorSettingsStyleSectionProps {
|
||||
indicatorType: string;
|
||||
plots: PlotDef[];
|
||||
hlines: HLineDef[];
|
||||
cloudColors?: IchimokuCloudColors;
|
||||
showIchimokuCloud?: boolean;
|
||||
onPlotStyle: (idx: number, patch: Partial<PlotDef>) => void;
|
||||
onHLineStyle: (idx: number, patch: Partial<HLineDef>) => void;
|
||||
onHLinePrice?: (idx: number, price: number) => void;
|
||||
onCloudColors?: (patch: Partial<IchimokuCloudColors>) => void;
|
||||
}
|
||||
|
||||
function measureTitleColumn(sectionEl: HTMLElement | null, measureEl: HTMLElement | null) {
|
||||
if (!sectionEl || !measureEl) return;
|
||||
let maxW = 0;
|
||||
measureEl.querySelectorAll<HTMLElement>('.ism-plot-title').forEach(el => {
|
||||
maxW = Math.max(maxW, el.offsetWidth);
|
||||
});
|
||||
if (maxW > 0) {
|
||||
sectionEl.style.setProperty('--ism-plot-title-width', `${Math.ceil(maxW)}px`);
|
||||
}
|
||||
}
|
||||
|
||||
const PlotStyleRow: React.FC<{
|
||||
label: string;
|
||||
plot: PlotDef;
|
||||
disabled?: boolean;
|
||||
onStyle: (patch: Partial<PlotDef>) => void;
|
||||
}> = ({ label, plot, disabled, onStyle }) => {
|
||||
const isHistogram = plot.type === 'histogram';
|
||||
const lineWidth = plot.lineWidth ?? 1;
|
||||
const lineStyle = plot.lineStyle ?? 'solid';
|
||||
|
||||
return (
|
||||
<div className="ism-style-row">
|
||||
<div className="ism-plot-title-cell">
|
||||
<span className="ism-plot-title">{label}</span>
|
||||
</div>
|
||||
<div className="ism-style-field">
|
||||
<span className="ism-style-label">색상</span>
|
||||
<ColorInput
|
||||
value={plot.color}
|
||||
onChange={v => onStyle({ color: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className="ism-style-field">
|
||||
<span className="ism-style-label">굵기</span>
|
||||
{isHistogram ? (
|
||||
<span className="ism-style-field-spacer ism-num-combo-spacer" aria-hidden />
|
||||
) : (
|
||||
<select
|
||||
className="ism-select ism-style-width-select"
|
||||
value={lineWidth}
|
||||
disabled={disabled}
|
||||
onChange={e => onStyle({ lineWidth: Number(e.target.value) })}
|
||||
>
|
||||
{LINE_WIDTH_OPTIONS.map(w => (
|
||||
<option key={w} value={w}>{w}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
<div className="ism-style-field">
|
||||
<span className="ism-style-label">타입</span>
|
||||
{isHistogram ? (
|
||||
<span className="ism-style-type-static">히스토그램</span>
|
||||
) : (
|
||||
<select
|
||||
className="ism-select ism-style-type-select"
|
||||
value={lineStyle}
|
||||
disabled={disabled}
|
||||
onChange={e => onStyle({ lineStyle: e.target.value as HLineStyle })}
|
||||
>
|
||||
{LINE_STYLE_OPTIONS.map(s => (
|
||||
<option key={s} value={s}>{LINE_STYLE_TAB_LABELS[s]}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HLineStyleRow: React.FC<{
|
||||
indicatorType: string;
|
||||
label: string;
|
||||
hl: HLineDef;
|
||||
onStyle: (patch: Partial<HLineDef>) => void;
|
||||
onPrice?: (price: number) => void;
|
||||
}> = ({ indicatorType, label, hl, onStyle, onPrice }) => {
|
||||
const isOn = hl.visible !== false;
|
||||
const lineWidth = hl.lineWidth ?? 1;
|
||||
const lineStyle = hl.lineStyle ?? 'dashed';
|
||||
|
||||
return (
|
||||
<div className={`ism-hline-row${isOn ? '' : ' ism-hline-row--off'}`}>
|
||||
<div className="ism-hline-name-cell">
|
||||
<span className="ism-hline-label-text">{label}</span>
|
||||
</div>
|
||||
<div className="ism-hline-cell">
|
||||
{onPrice != null ? (
|
||||
<NumericParamInput
|
||||
className="ism-input ism-narrow"
|
||||
value={hl.price}
|
||||
spec={getHlinePriceSpec(indicatorType, hl.price)}
|
||||
disabled={!isOn}
|
||||
onChange={onPrice}
|
||||
/>
|
||||
) : (
|
||||
<span className="ism-hline-price-readonly">{hl.price}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ism-hline-cell ism-hline-cell--style">
|
||||
<ColorInput value={hl.color} onChange={v => onStyle({ color: v })} />
|
||||
<select
|
||||
className="ism-select ism-style-width-select"
|
||||
value={lineWidth}
|
||||
disabled={!isOn}
|
||||
title="굵기"
|
||||
onChange={e => onStyle({ lineWidth: Number(e.target.value) })}
|
||||
>
|
||||
{LINE_WIDTH_OPTIONS.map(w => (
|
||||
<option key={w} value={w}>{w}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="ism-select ism-style-type-select"
|
||||
value={lineStyle}
|
||||
disabled={!isOn}
|
||||
title="타입"
|
||||
onChange={e => onStyle({ lineStyle: e.target.value as HLineStyle })}
|
||||
>
|
||||
{LINE_STYLE_OPTIONS.map(s => (
|
||||
<option key={s} value={s}>{LINE_STYLE_TAB_LABELS[s]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const IndicatorSettingsStyleSection: React.FC<IndicatorSettingsStyleSectionProps> = ({
|
||||
indicatorType,
|
||||
plots,
|
||||
hlines,
|
||||
cloudColors,
|
||||
showIchimokuCloud,
|
||||
onPlotStyle,
|
||||
onHLineStyle,
|
||||
onHLinePrice,
|
||||
onCloudColors,
|
||||
}) => {
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const measureRef = useRef<HTMLDivElement>(null);
|
||||
const hlinePrices = hlines.map(h => h.price);
|
||||
|
||||
const plotLabel = (p: PlotDef) => indicatorType === 'IchimokuCloud'
|
||||
? getIchimokuPlotTitle(p.id)
|
||||
: getPlotLabel(p.title);
|
||||
|
||||
const titleLabels = [
|
||||
...plots.map(plotLabel),
|
||||
...hlines.map(h => h.label ?? getHLineLabel(h.price, hlinePrices)),
|
||||
];
|
||||
|
||||
useLayoutEffect(() => {
|
||||
measureTitleColumn(sectionRef.current, measureRef.current);
|
||||
}, [titleLabels.join('\0'), plots.length, hlines.length]);
|
||||
|
||||
return (
|
||||
<div className="ism-section ism-style-tab-section">
|
||||
<div ref={measureRef} className="ism-plot-title-measure" aria-hidden>
|
||||
{titleLabels.map((t, i) => (
|
||||
<span key={i} className="ism-plot-title">{t}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div ref={sectionRef} className="ism-style-section">
|
||||
{plots.map((plot, idx) => (
|
||||
<PlotStyleRow
|
||||
key={plot.id}
|
||||
label={plotLabel(plot)}
|
||||
plot={plot}
|
||||
onStyle={patch => onPlotStyle(idx, patch)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{hlines.length > 0 && (
|
||||
<>
|
||||
<hr className="ism-hline-divider" />
|
||||
<div className="ism-hline-header-row">
|
||||
<span>이름</span>
|
||||
<span>가격</span>
|
||||
<span>색상 · 굵기 · 타입</span>
|
||||
</div>
|
||||
{hlines.map((hl, idx) => (
|
||||
<HLineStyleRow
|
||||
key={idx}
|
||||
indicatorType={indicatorType}
|
||||
label={hl.label ?? getHLineLabel(hl.price, hlinePrices)}
|
||||
hl={hl}
|
||||
onStyle={patch => onHLineStyle(idx, patch)}
|
||||
onPrice={onHLinePrice ? v => onHLinePrice(idx, v) : undefined}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showIchimokuCloud && cloudColors && onCloudColors && (
|
||||
<IchimokuCloudSection cloudColors={cloudColors} onChange={onCloudColors} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndicatorSettingsStyleSection;
|
||||
Reference in New Issue
Block a user