mobile download

This commit is contained in:
Macbook
2026-05-28 14:44:19 +09:00
parent e2816b037f
commit 3503ef33f5
152 changed files with 11021 additions and 687 deletions
+106
View File
@@ -0,0 +1,106 @@
/**
* UI 설정 — gc_app_settings.ui_preferences_json (DB 단일 소스)
*/
import {
getAppSettingsCache,
subscribeAppSettings,
} from '../hooks/useAppSettings';
import { saveAppSettings, type AppSettingsDto } from './backendApi';
import {
EMPTY_UI_PREFERENCES,
UI_PREFERENCES_VERSION,
type UiPreferences,
} from '../types/uiPreferences';
const SAVE_DEBOUNCE_MS = 400;
let saveTimer: ReturnType<typeof setTimeout> | null = null;
let pendingPatch: Partial<UiPreferences> | null = null;
function isPlainObject(v: unknown): v is Record<string, unknown> {
return v != null && typeof v === 'object' && !Array.isArray(v);
}
function deepMerge<T extends Record<string, unknown>>(base: T, patch: Partial<T>): T {
const out = { ...base } as T;
for (const key of Object.keys(patch) as (keyof T)[]) {
const pv = patch[key];
if (pv === undefined) continue;
const bv = base[key];
if (isPlainObject(bv) && isPlainObject(pv)) {
out[key] = deepMerge(bv as Record<string, unknown>, pv as Record<string, unknown>) as T[keyof T];
} else {
out[key] = pv as T[keyof T];
}
}
return out;
}
export function resolveUiPreferences(raw: unknown): UiPreferences {
if (!raw || typeof raw !== 'object') {
return { ...EMPTY_UI_PREFERENCES };
}
const o = raw as UiPreferences;
return {
...EMPTY_UI_PREFERENCES,
...o,
v: UI_PREFERENCES_VERSION,
strategyEditor: { ...EMPTY_UI_PREFERENCES.strategyEditor, ...o.strategyEditor },
indicator: { ...EMPTY_UI_PREFERENCES.indicator, ...o.indicator },
chart: { ...EMPTY_UI_PREFERENCES.chart, ...o.chart },
panels: { ...EMPTY_UI_PREFERENCES.panels, ...o.panels },
virtual: { ...EMPTY_UI_PREFERENCES.virtual, ...o.virtual },
tradeNotifications: { ...EMPTY_UI_PREFERENCES.tradeNotifications, ...o.tradeNotifications },
};
}
/** DB 캐시 기준 UI 설정 (앱 설정 로드 후 유효) */
export function getUiPreferences(): UiPreferences {
return resolveUiPreferences(getAppSettingsCache().uiPreferences);
}
/** UI 설정 부분 갱신 — 낙관적 캐시 + 디바운스 DB 저장 */
export function patchUiPreferences(patch: Partial<UiPreferences>, immediate = false): void {
const current = getUiPreferences();
const next = deepMerge(current as Record<string, unknown>, patch as Record<string, unknown>) as UiPreferences;
next.v = UI_PREFERENCES_VERSION;
const cache = getAppSettingsCache();
const merged: AppSettingsDto = { ...cache, uiPreferences: next };
Object.assign(cache, merged);
pendingPatch = pendingPatch
? (deepMerge(pendingPatch as Record<string, unknown>, patch as Record<string, unknown>) as Partial<UiPreferences>)
: patch;
const flush = () => {
saveTimer = null;
const toSave = pendingPatch
? resolveUiPreferences(deepMerge(getUiPreferences() as Record<string, unknown>, pendingPatch as Record<string, unknown>))
: next;
pendingPatch = null;
void saveAppSettings({ uiPreferences: toSave }).catch(err => {
console.warn('[uiPreferences] DB 저장 실패:', err);
});
};
if (immediate) {
if (saveTimer != null) clearTimeout(saveTimer);
pendingPatch = null;
void saveAppSettings({ uiPreferences: next }).catch(err => {
console.warn('[uiPreferences] DB 저장 실패:', err);
});
return;
}
if (saveTimer != null) clearTimeout(saveTimer);
saveTimer = setTimeout(flush, SAVE_DEBOUNCE_MS);
}
export function subscribeUiPreferences(listener: () => void): () => void {
return subscribeAppSettings(listener);
}
/** 중첩 경로 헬퍼 */
export function getUiPref<K extends keyof UiPreferences>(key: K): UiPreferences[K] {
return getUiPreferences()[key];
}