보조지표 합치기, 분리, 복사하기 기능 오류 수정

This commit is contained in:
Macbook
2026-06-01 01:50:54 +09:00
parent f2208aab99
commit 227db1e607
4 changed files with 110 additions and 35 deletions
+28 -15
View File
@@ -1268,9 +1268,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
const currSK = styleKey(indicators); const currSK = styleKey(indicators);
const needsRecalc = prevPK !== currPK; const needsRecalc = prevPK !== currPK;
const isReorderOnly = needsRecalc const chartChange = classifyIndicatorChartChange(prevList, indicators);
&& sortedParamKey(indicators) === sortedParamKey(prevList)
&& currSK === prevSK;
const prevMap = new Map(prevList.map(i => [i.id, i])); const prevMap = new Map(prevList.map(i => [i.id, i]));
const removedIds = prevList const removedIds = prevList
@@ -1283,7 +1281,17 @@ const TradingChart: React.FC<TradingChartProps> = ({
return singleIndParamKey(p) !== singleIndParamKey(c); return singleIndParamKey(p) !== singleIndParamKey(c);
}); });
if (!needsRecalc && prevSK !== currSK) { if (
chartChange.mode === 'reorder-only'
|| chartChange.mode === 'layout-changed'
) {
/** 순서·병합·분리 — 설정에 맞게 sub-pane 전체 재배치 */
indicatorSyncInFlightRef.current = true;
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
});
} else if (!needsRecalc && prevSK !== currSK) {
for (const ind of indicators) { for (const ind of indicators) {
mgr.applyIndicatorStyle(ind); mgr.applyIndicatorStyle(ind);
} }
@@ -1336,7 +1344,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
) { ) {
const savedLR = mgr.getVisibleLogicalRange(); const savedLR = mgr.getVisibleLogicalRange();
indicatorSyncInFlightRef.current = true; indicatorSyncInFlightRef.current = true;
void mgr.addIndicatorsBatch(addedConfigs).then(() => { /** 복사 등 목록 중간 삽입 — pane 순서를 맞추려면 전체 재로드 */
const insertIdx = addedConfigs.length === 1
? indicators.findIndex(i => i.id === addedConfigs[0].id)
: -1;
const reloadForPaneOrder = insertIdx >= 0 && insertIdx < indicators.length - 1;
const finishAdd = () => {
afterIndicatorPaneMutation(mgr); afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged(); mgr.notifyPaneLayoutChanged();
restoreLogicalRange(mgr, savedLR); restoreLogicalRange(mgr, savedLR);
@@ -1349,7 +1363,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
setTimeout(() => mgr.notifyPaneLayoutChanged(), ms); setTimeout(() => mgr.notifyPaneLayoutChanged(), ms);
}); });
})); }));
}); };
if (reloadForPaneOrder) {
reloadIndicatorsWithCover(mgr, indicators, finishAdd);
} else {
void mgr.addIndicatorsBatch(addedConfigs).then(finishAdd);
}
} else if ( } else if (
paramsChangedConfigs.length > 0 paramsChangedConfigs.length > 0
&& removedIds.length === 0 && removedIds.length === 0
@@ -1373,19 +1393,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
setPaneLegendPaused(false); setPaneLegendPaused(false);
flushPendingIndicatorResync(mgr, syncGen); flushPendingIndicatorResync(mgr, syncGen);
}); });
} else if (isReorderOnly) { } else if (chartChange.mode === 'full') {
reloadIndicatorsWithCover(mgr, indicators, () => { reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators); syncIndicatorTrackingRefs(indicators);
}); });
} else { } else {
const change = classifyIndicatorChartChange(prevList, indicators); syncIndicatorTrackingRefs(indicators);
if (change.mode === 'full') {
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
});
} else {
syncIndicatorTrackingRefs(indicators);
}
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
+68 -10
View File
@@ -34,7 +34,7 @@ import {
normalizeChartTimeFormat, normalizeChartTimeFormat,
} from './chartTimeFormat'; } from './chartTimeFormat';
import { DEFAULT_DISPLAY_TIMEZONE } from './timezone'; import { DEFAULT_DISPLAY_TIMEZONE } from './timezone';
import { sortIndicatorsForPaneLoad } from './indicatorPaneMerge'; import { getPaneHostId, sortIndicatorsForPaneLoad } from './indicatorPaneMerge';
import { calculateIndicator, enrichIndicatorConfig, getIndicatorDef, getHLineLabel, type PlotData, type MarkerData } from './indicatorRegistry'; import { calculateIndicator, enrichIndicatorConfig, getIndicatorDef, getHLineLabel, type PlotData, type MarkerData } from './indicatorRegistry';
import { IchimokuCloudPlugin, type IchimokuCloudPoint } from './IchimokuCloudPlugin'; import { IchimokuCloudPlugin, type IchimokuCloudPoint } from './IchimokuCloudPlugin';
@@ -808,6 +808,8 @@ export class ChartManager {
private _finalizeIndicatorPaneLayout(): void { private _finalizeIndicatorPaneLayout(): void {
this._removeOrphanSubPanes(); this._removeOrphanSubPanes();
if (this._activeIndicatorPaneIndices().size > 0) { if (this._activeIndicatorPaneIndices().size > 0) {
this._resyncIndicatorPaneIndices();
this._splitCollidingIndicatorPanes();
this._resyncIndicatorPaneIndices(); this._resyncIndicatorPaneIndices();
this._applyAllSeriesPriceFormats(); this._applyAllSeriesPriceFormats();
} else { } else {
@@ -929,6 +931,8 @@ export class ChartManager {
this._removeOrphanSubPanes(); this._removeOrphanSubPanes();
this._trimTrailingEmptySubPanes(); this._trimTrailingEmptySubPanes();
this._resyncIndicatorPaneIndices(); this._resyncIndicatorPaneIndices();
this._splitCollidingIndicatorPanes();
this._resyncIndicatorPaneIndices();
this.resetPaneHeights(this._lastLayoutAvailableHeight); this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout(); this._notifyPaneLayout();
} }
@@ -1420,21 +1424,71 @@ export class ChartManager {
private _collectUsedSubPaneIndices(): Set<number> { private _collectUsedSubPaneIndices(): Set<number> {
const indices = new Set<number>(); const indices = new Set<number>();
for (const entry of this.indicators.values()) { for (const entry of this.indicators.values()) {
let found = false; let pane = -1;
for (const series of entry.seriesList) { for (const series of entry.seriesList) {
const pi = this._readSeriesPaneIndex(series); const pi = this._readSeriesPaneIndex(series);
if (pi >= 2) { if (pi >= 2) pane = Math.max(pane, pi);
indices.add(pi);
found = true;
}
} }
if (!found && entry.paneIndex != null && entry.paneIndex >= 2) { if (pane < 2 && entry.paneIndex != null && entry.paneIndex >= 2) {
indices.add(entry.paneIndex); pane = entry.paneIndex;
} }
if (pane >= 2) indices.add(pane);
} }
return indices; return indices;
} }
private _indicatorConfigsFromMap(): IndicatorConfig[] {
return Array.from(this.indicators.values()).map(e => e.config);
}
/**
* 병합(mergedWith)이 아닌 서로 다른 호스트가 같은 sub-pane에 붙어 있으면 분리.
* entry.paneIndex 와 실제 시리즈 pane 불일치·지표 제거 후 재추가 시 겹침 방지.
*/
private _splitCollidingIndicatorPanes(): void {
const configs = this._indicatorConfigsFromMap();
const hostsByPane = new Map<number, string[]>();
for (const [id, entry] of this.indicators) {
const hostId = getPaneHostId(id, configs);
if (id !== hostId) continue;
const def = getIndicatorDef(entry.type);
if (def?.overlay || def?.returnsMarkers || entry.seriesList.length === 0) continue;
let pi = -1;
for (const series of entry.seriesList) {
pi = Math.max(pi, this._readSeriesPaneIndex(series));
}
if (pi < 2 && entry.paneIndex != null && entry.paneIndex >= 2) pi = entry.paneIndex;
if (pi < 2) continue;
const list = hostsByPane.get(pi) ?? [];
if (!list.includes(hostId)) list.push(hostId);
hostsByPane.set(pi, list);
}
let changed = false;
for (const hostIds of hostsByPane.values()) {
if (hostIds.length <= 1) continue;
for (let i = 1; i < hostIds.length; i++) {
const hostId = hostIds[i];
const target = this._getNextSubPane();
for (const [mid, ment] of this.indicators) {
if (getPaneHostId(mid, configs) !== hostId) continue;
for (const series of ment.seriesList) {
try {
series.moveToPane(target);
} catch { /* ok */ }
}
ment.paneIndex = target;
}
changed = true;
}
}
if (changed) this._removeOrphanSubPanes();
}
private _getNextSubPane(): number { private _getNextSubPane(): number {
const used = this._collectUsedSubPaneIndices(); const used = this._collectUsedSubPaneIndices();
let pane = 2; let pane = 2;
@@ -1815,9 +1869,13 @@ export class ChartManager {
this._reapplyAllPatternMarkers(); this._reapplyAllPatternMarkers();
this._removeExtraSubPanes(); this._removeExtraSubPanes();
for (const cfg of configs) { const sorted = sortIndicatorsForPaneLoad(configs);
for (const cfg of sorted) {
if (this._indicatorLoadStale(loadGen)) break; if (this._indicatorLoadStale(loadGen)) break;
await this.addIndicator(cfg); await this.addIndicator(cfg, { skipLayout: true });
}
if (!this._indicatorLoadStale(loadGen)) {
this._finalizeIndicatorPaneLayout();
} }
} }
+14
View File
@@ -1,4 +1,5 @@
import type { IndicatorConfig } from '../types'; import type { IndicatorConfig } from '../types';
import { layoutKey } from './indicatorPaneMerge';
export type IndicatorChartSyncMode = export type IndicatorChartSyncMode =
| 'none' | 'none'
@@ -6,6 +7,7 @@ export type IndicatorChartSyncMode =
| 'add-only' | 'add-only'
| 'params-changed' | 'params-changed'
| 'reorder-only' | 'reorder-only'
| 'layout-changed'
| 'full'; | 'full';
export interface IndicatorChartChange { export interface IndicatorChartChange {
@@ -62,6 +64,9 @@ export function classifyIndicatorChartChange(
}; };
if (removedIds.length === 0 && added.length === 0 && paramsChangedIds.length === 0) { if (removedIds.length === 0 && added.length === 0 && paramsChangedIds.length === 0) {
if (layoutKey(prev) !== layoutKey(curr)) {
return { ...empty, mode: 'layout-changed' };
}
const sameSet = prev.length === curr.length const sameSet = prev.length === curr.length
&& prev.every(p => currMap.has(p.id)); && prev.every(p => currMap.has(p.id));
if (sameSet && prev.map(i => i.id).join(',') !== curr.map(i => i.id).join(',')) { if (sameSet && prev.map(i => i.id).join(',') !== curr.map(i => i.id).join(',')) {
@@ -70,6 +75,15 @@ export function classifyIndicatorChartChange(
return empty; return empty;
} }
/** 병합·분리 등 mergedWith 변경 — params 키에도 잡히지만 pane 재배치가 필요 */
if (
removedIds.length === 0
&& added.length === 0
&& layoutKey(prev) !== layoutKey(curr)
) {
return { ...empty, mode: 'layout-changed', paramsChangedIds };
}
if (removedIds.length > 0 && added.length === 0 && paramsChangedIds.length === 0) { if (removedIds.length > 0 && added.length === 0 && paramsChangedIds.length === 0) {
return { ...empty, mode: 'remove-only' }; return { ...empty, mode: 'remove-only' };
} }
-10
View File
@@ -21,7 +21,6 @@
"@capacitor/haptics": "^7.0.0", "@capacitor/haptics": "^7.0.0",
"@capacitor/ios": "^7.0.0", "@capacitor/ios": "^7.0.0",
"@capacitor/preferences": "^7.0.0", "@capacitor/preferences": "^7.0.0",
"@capacitor/push-notifications": "^7.0.0",
"@capacitor/splash-screen": "^7.0.0", "@capacitor/splash-screen": "^7.0.0",
"@capacitor/status-bar": "^7.0.0", "@capacitor/status-bar": "^7.0.0",
"@goldenchart/shared": "*", "@goldenchart/shared": "*",
@@ -472,15 +471,6 @@
"@capacitor/core": ">=7.0.0" "@capacitor/core": ">=7.0.0"
} }
}, },
"node_modules/@capacitor/push-notifications": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@capacitor/push-notifications/-/push-notifications-7.0.6.tgz",
"integrity": "sha512-zAhbHpdbc15ImuVGgoFwUZsKI+jjGxy/oO5mgfYKUx8Xl2OskndzhL79PYsCPjyGLbqUR9NRUZJthV9auVT3nw==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
}
},
"node_modules/@capacitor/splash-screen": { "node_modules/@capacitor/splash-screen": {
"version": "7.0.5", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-7.0.5.tgz", "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-7.0.5.tgz",