/** * localStorage → gc_app_settings.ui_preferences_json 1회 이전 */ import type { AppSettingsDto } from './backendApi'; import type { UiPreferences } from '../types/uiPreferences'; import { resolveUiPreferences } from './uiPreferencesDb'; const MIGRATED_FLAG = 'gc_ui_prefs_migrated_v1'; function readJson(key: string): T | null { try { const raw = localStorage.getItem(key); if (!raw) return null; return JSON.parse(raw) as T; } catch { return null; } } function removeKeys(keys: string[]): void { for (const k of keys) { try { localStorage.removeItem(k); } catch { /* ignore */ } } } /** 이미 DB에 uiPreferences가 있으면 스킵. 없으면 localStorage 병합 후 키 제거. */ export function migrateLocalStorageToUiPreferences( current: AppSettingsDto, ): { uiPreferences: UiPreferences; changed: boolean } | null { try { if (localStorage.getItem(MIGRATED_FLAG) === '1' && current.uiPreferences) { return null; } } catch { /* ignore */ } const base = resolveUiPreferences(current.uiPreferences); const patch: UiPreferences = { ...base }; let changed = false; const editorMode = localStorage.getItem('gc_se_editor_mode'); if (editorMode === 'list' || editorMode === 'graph') { patch.strategyEditor = { ...patch.strategyEditor, editorMode }; changed = true; } const canvasMode = localStorage.getItem('gc_se_canvas_interaction'); if (canvasMode) { patch.strategyEditor = { ...patch.strategyEditor, canvasInteraction: canvasMode }; changed = true; } const paletteAux = readJson('se-palette-auxiliary-v1'); if (paletteAux?.length) { patch.strategyEditor = { ...patch.strategyEditor, paletteAuxiliary: paletteAux as NonNullable['paletteAuxiliary'] }; changed = true; } const paletteComp = readJson('se-palette-composite-v1'); if (paletteComp?.length) { patch.strategyEditor = { ...patch.strategyEditor, paletteComposite: paletteComp as NonNullable['paletteComposite'] }; changed = true; } const customTabs = readJson['customTabs']>('gc-indicator-custom-tabs-v1' as never); if (customTabs?.length) { patch.indicator = { ...patch.indicator, customTabs }; changed = true; } const mainTabOrder = readJson('gc_indicator_main_tab_order'); if (mainTabOrder?.length) { patch.indicator = { ...patch.indicator, mainTabOrder }; changed = true; } const listOrder = readJson('gc_indicator_settings_list_order'); if (listOrder?.length) { patch.indicator = { ...patch.indicator, listOrder }; changed = true; } const chartState = readJson<{ alertPrices?: number[]; indicators?: unknown[] }>('trading_chart_state'); if (chartState?.alertPrices?.length || chartState?.indicators?.length) { patch.chart = { ...patch.chart, alertPrices: chartState.alertPrices, legacyIndicators: chartState.indicators as NonNullable['legacyIndicators'], }; changed = true; } const panels: Record = { ...patch.panels }; for (const key of ['se-left-width', 'se-terminal-height', 'btd-left-width', 'btd-right-width']) { const v = localStorage.getItem(key); if (v != null) { const n = Number(v); panels[key] = Number.isFinite(n) ? n : v; changed = true; } } const virtualTargets = readJson('gc_virtual_targets_v1'); const virtualSession = readJson('gc_virtual_session_v1'); const cardView = localStorage.getItem('gc_virtual_card_view_v1'); if (virtualTargets?.length || virtualSession || cardView) { patch.virtual = { ...patch.virtual, ...(virtualTargets?.length ? { targets: virtualTargets as NonNullable['targets'] } : {}), ...(virtualSession ? { session: virtualSession as NonNullable['session'] } : {}), ...(cardView === 'detail' || cardView === 'summary' ? { cardViewMode: cardView } : {}), }; changed = true; } const readIds = readJson('gc_trade_notify_read_v1'); const hiddenIds = readJson('gc_trade_notify_hidden_v1'); if (readIds?.length || hiddenIds?.length) { patch.tradeNotifications = { readIds: readIds ?? [], hiddenIds: hiddenIds ?? [], }; changed = true; } const displayMode = localStorage.getItem('tsd-display-mode'); if (displayMode === 'chart' || displayMode === 'summary') { changed = true; } if (!changed && current.uiPreferences) { try { localStorage.setItem(MIGRATED_FLAG, '1'); } catch { /* ignore */ } return null; } if (!changed) return null; removeKeys([ 'gc_se_editor_mode', 'gc_se_canvas_interaction', 'se-palette-auxiliary-v1', 'se-palette-composite-v1', 'gc-indicator-custom-tabs-v1', 'gc_indicator_main_tab_order', 'gc_indicator_settings_list_order', 'trading_chart_state', 'chartLayout', 'chartLayoutSync', 'gc_virtual_targets_v1', 'gc_virtual_session_v1', 'gc_virtual_card_view_v1', 'gc_trade_notify_read_v1', 'gc_trade_notify_hidden_v1', 'tsd-display-mode', 'gc_se_flow_layout_v1', 'gc_strat_v2', 'gc_strategies_v1', 'se-left-width', 'se-terminal-height', 'btd-left-width', 'btd-right-width', ]); try { localStorage.setItem(MIGRATED_FLAG, '1'); } catch { /* ignore */ } return { uiPreferences: resolveUiPreferences(patch), changed: true }; }