goldenChat base source add
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
import React from 'react';
|
||||
import type { IndicatorConfig, HlinesBackground, Timeframe } from '../types';
|
||||
import type { PlotDef, HLineDef } from '../utils/indicatorRegistry';
|
||||
import { getHLineLabel } from '../utils/indicatorRegistry';
|
||||
import { getParamLabel, getPlotLabel, getSrcOptionLabel } from '../utils/indicatorLabels';
|
||||
import { getIchimokuPlotTitle } from '../utils/ichimokuConfig';
|
||||
import {
|
||||
getNumericParamSpec,
|
||||
getHlinePriceSpec,
|
||||
} from '../utils/indicatorParamSpec';
|
||||
import {
|
||||
getGlobalParamKeys,
|
||||
getPlotParamKeys,
|
||||
plotParamsOnGlobalRowOnly,
|
||||
plotRowHasNoInputs,
|
||||
} from '../utils/indicatorSettingsLayout';
|
||||
import NumericParamInput from './NumericParamInput';
|
||||
import PlotLineStylePicker from './PlotLineStylePicker';
|
||||
import ColorInput from './ColorInput';
|
||||
import { colorToRgbaString, cloudColorOpacityPercent } from '../utils/ichimokuConfig';
|
||||
import type { IchimokuCloudColors } from '../utils/ichimokuConfig';
|
||||
|
||||
const SRC_OPTIONS = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4'];
|
||||
|
||||
const MA_TYPE_OPTIONS_CCI = ['None', 'SMA', 'EMA', 'WMA'] as const;
|
||||
const MA_TYPE_OPTIONS_OBV = ['SMA', 'EMA', 'WMA'] as const;
|
||||
|
||||
/** 업비트 OBV 드롭다운 표기 */
|
||||
const OBV_MA_TYPE_LABELS: Record<string, string> = {
|
||||
SMA: '단순이동평균',
|
||||
EMA: '지수 이동 평균',
|
||||
WMA: '가중이동평균',
|
||||
};
|
||||
|
||||
const MA_TYPE_LABELS: Record<string, string> = {
|
||||
None: '없음 (None)',
|
||||
SMA: '단순이동평균 (SMA)',
|
||||
EMA: '지수이동평균 (EMA)',
|
||||
WMA: '가중이동평균 (WMA)',
|
||||
'SMMA (RMA)': 'RMA (SMMA)',
|
||||
VWMA: '거래량가중이동평균 (VWMA)',
|
||||
};
|
||||
|
||||
function isMaTypeParam(indicatorType: string, key: string, value: unknown): boolean {
|
||||
if (key !== 'maType' || typeof value !== 'string') return false;
|
||||
return indicatorType === 'CCI' || indicatorType === 'OBV' || indicatorType === 'RSI';
|
||||
}
|
||||
|
||||
function maTypeOptionsFor(indicatorType: string): readonly string[] {
|
||||
if (indicatorType === 'OBV') return MA_TYPE_OPTIONS_OBV;
|
||||
return MA_TYPE_OPTIONS_CCI;
|
||||
}
|
||||
|
||||
function isSrcParam(key: string, value: unknown): boolean {
|
||||
return (key === 'src' || key.toLowerCase().includes('src')) && typeof value === 'string';
|
||||
}
|
||||
|
||||
export { ColorInput };
|
||||
|
||||
/* ── 단일 파라미터 컨트롤 ─────────────────────────────────────── */
|
||||
export const ParamField: React.FC<{
|
||||
indicatorType: string;
|
||||
paramKey: string;
|
||||
value: number | string | boolean;
|
||||
disabled?: boolean;
|
||||
onChange: (key: string, raw: string | boolean) => void;
|
||||
}> = ({ indicatorType, paramKey, value, disabled, onChange }) => {
|
||||
if (typeof value === 'boolean') {
|
||||
return (
|
||||
<label className="ism-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
disabled={disabled}
|
||||
onChange={e => onChange(paramKey, e.target.checked)}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
if (isMaTypeParam(indicatorType, paramKey, value)) {
|
||||
return (
|
||||
<select
|
||||
className="ism-select ism-param-inline-select"
|
||||
value={value as string}
|
||||
disabled={disabled}
|
||||
onChange={e => onChange(paramKey, e.target.value)}
|
||||
>
|
||||
{maTypeOptionsFor(indicatorType).map(o => (
|
||||
<option key={o} value={o}>
|
||||
{indicatorType === 'OBV'
|
||||
? (OBV_MA_TYPE_LABELS[o] ?? o)
|
||||
: (MA_TYPE_LABELS[o] ?? o)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
if (isSrcParam(paramKey, value)) {
|
||||
return (
|
||||
<select
|
||||
className="ism-select ism-param-inline-select"
|
||||
value={value as string}
|
||||
disabled={disabled}
|
||||
onChange={e => onChange(paramKey, e.target.value)}
|
||||
>
|
||||
{SRC_OPTIONS.map(o => (
|
||||
<option key={o} value={o}>{getSrcOptionLabel(o)}</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return (
|
||||
<NumericParamInput
|
||||
className="ism-input ism-narrow ism-sma-period-input"
|
||||
value={value}
|
||||
spec={getNumericParamSpec(indicatorType, paramKey, value)}
|
||||
disabled={disabled}
|
||||
onChange={v => onChange(paramKey, String(v))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
className="ism-input ism-narrow"
|
||||
value={value as string}
|
||||
disabled={disabled}
|
||||
onChange={e => onChange(paramKey, e.target.value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── 플롯 설정 행 (on/off · 이름 · 입력 · 색상·선) ─────────────── */
|
||||
export const PlotSettingsRow: React.FC<{
|
||||
indicatorType: string;
|
||||
plot: PlotDef;
|
||||
plotIndex: number;
|
||||
plots: PlotDef[];
|
||||
params: Record<string, number | string | boolean>;
|
||||
enabled: boolean;
|
||||
inputsOnly?: boolean;
|
||||
onToggle: (plotId: string, on: boolean) => void;
|
||||
onParamChange: (key: string, raw: string | boolean) => void;
|
||||
onPlotStyle: (plotIndex: number, patch: Partial<PlotDef>) => void;
|
||||
}> = ({
|
||||
indicatorType, plot, plotIndex, plots, params, enabled, inputsOnly,
|
||||
onToggle, onParamChange, onPlotStyle,
|
||||
}) => {
|
||||
const paramKeys = getPlotParamKeys(indicatorType, plot.id, plotIndex, plots, params);
|
||||
const noInputs = plotRowHasNoInputs(indicatorType, plot.id, plotIndex, plots, params);
|
||||
const globalOnlyInputs = plotParamsOnGlobalRowOnly(indicatorType, plot.id);
|
||||
const label = indicatorType === 'IchimokuCloud'
|
||||
? getIchimokuPlotTitle(plot.id)
|
||||
: getPlotLabel(plot.title);
|
||||
const isHistogram = plot.type === 'histogram';
|
||||
|
||||
return (
|
||||
<div className={`ism-row ism-sma-ma-row${enabled ? '' : ' ism-sma-ma-off'}`}>
|
||||
<label className="ism-toggle ism-sma-ma-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={e => onToggle(plot.id, e.target.checked)}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
<label className="ism-label ism-sma-ma-label">{label}</label>
|
||||
<div className="ism-control ism-sma-ma-control">
|
||||
{noInputs && globalOnlyInputs ? (
|
||||
<span className="ism-hlines-bg-spacer" aria-hidden />
|
||||
) : noInputs ? (
|
||||
<span className="ism-ichimoku-auto-param" title="상위 설정 또는 자동 계산">자동</span>
|
||||
) : (
|
||||
<div className="ism-plot-param-inputs">
|
||||
{paramKeys.map(key => (
|
||||
<ParamField
|
||||
key={key}
|
||||
indicatorType={indicatorType}
|
||||
paramKey={key}
|
||||
value={params[key]}
|
||||
disabled={!enabled}
|
||||
onChange={onParamChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!inputsOnly && (
|
||||
isHistogram ? (
|
||||
<ColorInput
|
||||
value={plot.color}
|
||||
onChange={v => onPlotStyle(plotIndex, { color: v })}
|
||||
/>
|
||||
) : (
|
||||
<PlotLineStylePicker
|
||||
disabled={!enabled}
|
||||
title={label}
|
||||
value={{
|
||||
color: plot.color,
|
||||
lineWidth: plot.lineWidth,
|
||||
lineStyle: plot.lineStyle,
|
||||
}}
|
||||
onChange={patch => onPlotStyle(plotIndex, patch)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── 전역 파라미터 행 (토글 없음) ─────────────────────────────── */
|
||||
export const GlobalParamRow: React.FC<{
|
||||
indicatorType: string;
|
||||
paramKey: string;
|
||||
value: number | string | boolean;
|
||||
onChange: (key: string, raw: string | boolean) => void;
|
||||
}> = ({ indicatorType, paramKey, value, onChange }) => (
|
||||
<div className="ism-row ism-global-param-row">
|
||||
<span className="ism-label ism-sma-ma-label">{getParamLabel(paramKey, indicatorType)}</span>
|
||||
<div className="ism-control ism-sma-ma-control">
|
||||
<ParamField
|
||||
indicatorType={indicatorType}
|
||||
paramKey={paramKey}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/* ── 수평선 행 ───────────────────────────────────────────────── */
|
||||
export const HLineSettingsRow: React.FC<{
|
||||
indicatorType: string;
|
||||
hl: HLineDef;
|
||||
idx: number;
|
||||
allPrices: number[];
|
||||
inputsOnly?: boolean;
|
||||
onVisible: (idx: number, v: boolean) => void;
|
||||
onPrice: (idx: number, v: number) => void;
|
||||
onStyle: (idx: number, patch: Partial<HLineDef>) => void;
|
||||
}> = ({ indicatorType, hl, idx, allPrices, inputsOnly, onVisible, onPrice, onStyle }) => {
|
||||
const label = hl.label ?? getHLineLabel(hl.price, allPrices);
|
||||
const isOn = hl.visible !== false;
|
||||
return (
|
||||
<div className={`ism-row ism-sma-ma-row${isOn ? '' : ' ism-sma-ma-off'}`}>
|
||||
<label className="ism-toggle ism-sma-ma-toggle">
|
||||
<input type="checkbox" checked={isOn} onChange={e => onVisible(idx, e.target.checked)} />
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
<label className="ism-label ism-sma-ma-label">{label}</label>
|
||||
<div className="ism-control ism-sma-ma-control">
|
||||
<NumericParamInput
|
||||
className="ism-input ism-narrow ism-sma-period-input"
|
||||
value={hl.price}
|
||||
spec={getHlinePriceSpec(indicatorType, hl.price)}
|
||||
disabled={!isOn}
|
||||
onChange={v => onPrice(idx, v)}
|
||||
/>
|
||||
{!inputsOnly && (
|
||||
<PlotLineStylePicker
|
||||
disabled={!isOn}
|
||||
title={label}
|
||||
value={{
|
||||
color: hl.color,
|
||||
lineWidth: hl.lineWidth,
|
||||
lineStyle: hl.lineStyle ?? 'dashed',
|
||||
}}
|
||||
onChange={patch => onStyle(idx, patch)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── 출력 · 타임프레임 (하단 공통) ───────────────────────────── */
|
||||
export const SettingsOutputFooter: React.FC<{
|
||||
lastValueVisible: boolean;
|
||||
onLastValueVisible: (v: boolean) => void;
|
||||
timeframeVis: Partial<Record<Timeframe, boolean>>;
|
||||
onTimeframeVis: (tf: Timeframe, v: boolean) => void;
|
||||
timeframes: { tf: Timeframe; label: string }[];
|
||||
/** 개별 설정 모달: 지표 전체 숨김 */
|
||||
chartHidden?: boolean;
|
||||
onChartHidden?: (showOnChart: boolean) => void;
|
||||
}> = ({
|
||||
lastValueVisible, onLastValueVisible, timeframeVis, onTimeframeVis, timeframes,
|
||||
chartHidden, onChartHidden,
|
||||
}) => (
|
||||
<>
|
||||
<div className="ism-ichimoku-cloud-divider" />
|
||||
<div className="ism-output-section-title">출력 · 표시</div>
|
||||
{onChartHidden != null && (
|
||||
<div className="ism-row ism-ichimoku-extra-row">
|
||||
<span className="ism-label">차트에 표시</span>
|
||||
<div className="ism-control">
|
||||
<label className="ism-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={chartHidden !== true}
|
||||
onChange={e => onChartHidden(e.target.checked)}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ism-row ism-ichimoku-extra-row">
|
||||
<span className="ism-label">가격축 라벨·설명</span>
|
||||
<div className="ism-control">
|
||||
<label className="ism-toggle" title="이 지표만 우측 가격축 금액·설명 표시 (차트 설정 전역 스위치가 켜져 있을 때 적용)">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={lastValueVisible}
|
||||
onChange={e => onLastValueVisible(e.target.checked)}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ism-tf-grid ism-ichimoku-tf-grid">
|
||||
{timeframes.map(({ tf, label }) => (
|
||||
<label key={tf} className="ism-tf-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={timeframeVis[tf] !== false}
|
||||
onChange={e => onTimeframeVis(tf, e.target.checked)}
|
||||
/>
|
||||
<span className="ism-tf-label">{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
/* ── 일목 구름 색상 ─────────────────────────────────────────── */
|
||||
export const IchimokuCloudSection: React.FC<{
|
||||
cloudColors: IchimokuCloudColors;
|
||||
onChange: (patch: Partial<IchimokuCloudColors>) => void;
|
||||
}> = ({ cloudColors, onChange }) => (
|
||||
<>
|
||||
<div className="ism-ichimoku-cloud-divider" />
|
||||
<div className="ism-ichimoku-cloud-section">
|
||||
<h4 className="ism-ichimoku-cloud-title">구름 영역 색상</h4>
|
||||
<p className="ism-ichimoku-cloud-hint">
|
||||
선행스팬1·선행스팬2가 모두 켜져 있을 때만 구름이 표시됩니다.
|
||||
</p>
|
||||
<div className="ism-ichimoku-cloud-grid">
|
||||
<div className="ism-ichimoku-cloud-card">
|
||||
<span className="ism-ichimoku-cloud-card-label">상승 구름 (선행1 > 선행2)</span>
|
||||
<div className="ism-ichimoku-cloud-card-row">
|
||||
<ColorInput
|
||||
value={cloudColors.bullishColor}
|
||||
onChange={v => onChange({ bullishColor: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className="ism-ichimoku-cloud-meta">
|
||||
{colorToRgbaString(cloudColors.bullishColor)}
|
||||
{' · '}투명도 {cloudColorOpacityPercent(cloudColors.bullishColor)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="ism-ichimoku-cloud-card">
|
||||
<span className="ism-ichimoku-cloud-card-label">하락 구름 (선행1 < 선행2)</span>
|
||||
<div className="ism-ichimoku-cloud-card-row">
|
||||
<ColorInput
|
||||
value={cloudColors.bearishColor}
|
||||
onChange={v => onChange({ bearishColor: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className="ism-ichimoku-cloud-meta">
|
||||
{colorToRgbaString(cloudColors.bearishColor)}
|
||||
{' · '}투명도 {cloudColorOpacityPercent(cloudColors.bearishColor)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
/* ── 볼린저밴드 백그라운드 (업비트 모습 탭) ───────────────────── */
|
||||
export const BollingerBackgroundRow: React.FC<{
|
||||
value: HlinesBackground;
|
||||
onChange: (v: HlinesBackground) => void;
|
||||
}> = ({ value, onChange }) => (
|
||||
<div className={`ism-row ism-sma-ma-row${value.visible ? '' : ' ism-sma-ma-off'}`}>
|
||||
<label className="ism-toggle ism-sma-ma-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value.visible}
|
||||
onChange={e => onChange({ ...value, visible: e.target.checked })}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
<span className="ism-label ism-sma-ma-label">백그라운드 그리기</span>
|
||||
<div className="ism-control ism-sma-ma-control">
|
||||
<span className="ism-ichimoku-auto-param ism-hlines-bg-spacer" aria-hidden />
|
||||
<ColorInput
|
||||
value={value.color}
|
||||
onChange={c => onChange({ ...value, color: c })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/* ── 수평선 배경 ─────────────────────────────────────────────── */
|
||||
export const HlinesBackgroundRow: React.FC<{
|
||||
value: HlinesBackground;
|
||||
onChange: (v: HlinesBackground) => void;
|
||||
}> = ({ value, onChange }) => (
|
||||
<>
|
||||
<div className="ism-ichimoku-cloud-divider" />
|
||||
<div className="ism-row ism-sma-ma-row">
|
||||
<label className="ism-toggle ism-sma-ma-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value.visible}
|
||||
onChange={e => onChange({ ...value, visible: e.target.checked })}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
<span className="ism-label ism-sma-ma-label">수평선 배경</span>
|
||||
<div className="ism-control ism-sma-ma-control">
|
||||
<span className="ism-ichimoku-auto-param ism-hlines-bg-spacer" aria-hidden />
|
||||
<ColorInput
|
||||
value={value.color}
|
||||
onChange={c => onChange({ ...value, color: c })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
/* ── 볼린저밴드 심볼 (업비트 인풋) ───────────────────────────── */
|
||||
export const BollingerSymbolSection: React.FC<{
|
||||
symbolMode: string;
|
||||
refSymbol: string;
|
||||
chartMarket: string;
|
||||
onChange: (patch: { symbolMode?: string; refSymbol?: string }) => void;
|
||||
}> = ({ symbolMode, refSymbol, chartMarket, onChange }) => {
|
||||
const isOther = symbolMode === 'other';
|
||||
return (
|
||||
<div className="ism-bb-symbol-section">
|
||||
<div className="ism-output-section-title">심볼</div>
|
||||
<label className="ism-bb-symbol-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="bb-symbol-mode"
|
||||
checked={!isOther}
|
||||
onChange={() => onChange({ symbolMode: 'main' })}
|
||||
/>
|
||||
<span>메인 차트 심볼</span>
|
||||
{chartMarket ? <span className="ism-bb-symbol-hint">{chartMarket}</span> : null}
|
||||
</label>
|
||||
<label className="ism-bb-symbol-option ism-bb-symbol-other">
|
||||
<input
|
||||
type="radio"
|
||||
name="bb-symbol-mode"
|
||||
checked={isOther}
|
||||
onChange={() => onChange({ symbolMode: 'other' })}
|
||||
/>
|
||||
<span>다른 심볼</span>
|
||||
<input
|
||||
type="text"
|
||||
className="ism-input ism-bb-ref-symbol"
|
||||
placeholder="KRW-BTC"
|
||||
value={refSymbol}
|
||||
disabled={!isOther}
|
||||
onChange={e => onChange({ refSymbol: e.target.value.toUpperCase() })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── 통합 본문 (일반 지표) ───────────────────────────────────── */
|
||||
export const UnifiedIndicatorBody: React.FC<{
|
||||
config: IndicatorConfig;
|
||||
params: Record<string, number | string | boolean>;
|
||||
plots: PlotDef[];
|
||||
plotVis: Record<string, boolean>;
|
||||
hlines: HLineDef[];
|
||||
hlinesBg: HlinesBackground;
|
||||
hasHLines: boolean;
|
||||
lastValueVisible: boolean;
|
||||
timeframeVis: Partial<Record<Timeframe, boolean>>;
|
||||
timeframes: { tf: Timeframe; label: string }[];
|
||||
onParamChange: (key: string, raw: string | boolean) => void;
|
||||
onPlotToggle: (plotId: string, on: boolean) => void;
|
||||
onPlotStyle: (idx: number, patch: Partial<PlotDef>) => void;
|
||||
onHLineVisible: (idx: number, v: boolean) => void;
|
||||
onHLinePrice: (idx: number, v: number) => void;
|
||||
onHLineStyle: (idx: number, patch: Partial<HLineDef>) => void;
|
||||
onHlinesBg: (v: HlinesBackground) => void;
|
||||
onLastValueVisible: (v: boolean) => void;
|
||||
onTimeframeVis: (tf: Timeframe, v: boolean) => void;
|
||||
cloudSection?: React.ReactNode;
|
||||
symbolSection?: React.ReactNode;
|
||||
/** 플롯 행 아래 추가 섹션 (BB 백그라운드 등) */
|
||||
afterPlotsSection?: React.ReactNode;
|
||||
/** false면 가격 스케일·타임프레임 보임 설정 숨김 (BB) */
|
||||
showOutputFooter?: boolean;
|
||||
inputsOnly?: boolean;
|
||||
chartHidden?: boolean;
|
||||
onChartHidden?: (showOnChart: boolean) => void;
|
||||
}> = (props) => {
|
||||
const {
|
||||
config, params, plots, plotVis, hlines, hlinesBg, hasHLines,
|
||||
lastValueVisible, timeframeVis, timeframes,
|
||||
onParamChange, onPlotToggle, onPlotStyle,
|
||||
onHLineVisible, onHLinePrice, onHLineStyle, onHlinesBg,
|
||||
onLastValueVisible, onTimeframeVis, cloudSection, symbolSection,
|
||||
afterPlotsSection,
|
||||
showOutputFooter = true,
|
||||
inputsOnly = false,
|
||||
chartHidden,
|
||||
onChartHidden,
|
||||
} = props;
|
||||
|
||||
const globalKeys = getGlobalParamKeys(config.type, params, plots);
|
||||
const hlinePrices = hlines.map(h => h.price);
|
||||
|
||||
return (
|
||||
<div className="ism-section unified-settings-section">
|
||||
{symbolSection}
|
||||
{symbolSection && globalKeys.length > 0 && <div className="ism-sma-divider" />}
|
||||
{globalKeys.map(key => (
|
||||
<GlobalParamRow
|
||||
key={key}
|
||||
indicatorType={config.type}
|
||||
paramKey={key}
|
||||
value={params[key]}
|
||||
onChange={onParamChange}
|
||||
/>
|
||||
))}
|
||||
|
||||
{globalKeys.length > 0 && plots.length > 0 && <div className="ism-sma-divider" />}
|
||||
|
||||
{plots.map((plot, idx) => (
|
||||
<PlotSettingsRow
|
||||
key={plot.id}
|
||||
indicatorType={config.type}
|
||||
plot={plot}
|
||||
plotIndex={idx}
|
||||
plots={plots}
|
||||
params={params}
|
||||
enabled={plotVis[plot.id] !== false}
|
||||
onToggle={onPlotToggle}
|
||||
onParamChange={onParamChange}
|
||||
onPlotStyle={onPlotStyle}
|
||||
inputsOnly={inputsOnly}
|
||||
/>
|
||||
))}
|
||||
|
||||
{cloudSection}
|
||||
|
||||
{afterPlotsSection}
|
||||
|
||||
{hlines.length > 0 && (
|
||||
<>
|
||||
<div className="ism-sma-divider" />
|
||||
<div className="ism-output-section-title">수평선</div>
|
||||
{hlines.map((hl, idx) => (
|
||||
<HLineSettingsRow
|
||||
key={idx}
|
||||
indicatorType={config.type}
|
||||
hl={hl}
|
||||
idx={idx}
|
||||
allPrices={hlinePrices}
|
||||
onVisible={onHLineVisible}
|
||||
onPrice={onHLinePrice}
|
||||
onStyle={onHLineStyle}
|
||||
inputsOnly={inputsOnly}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{hasHLines && !inputsOnly && <HlinesBackgroundRow value={hlinesBg} onChange={onHlinesBg} />}
|
||||
|
||||
{showOutputFooter && (
|
||||
<SettingsOutputFooter
|
||||
lastValueVisible={lastValueVisible}
|
||||
onLastValueVisible={onLastValueVisible}
|
||||
timeframeVis={timeframeVis}
|
||||
onTimeframeVis={onTimeframeVis}
|
||||
timeframes={timeframes}
|
||||
chartHidden={chartHidden}
|
||||
onChartHidden={onChartHidden}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user