110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
import type { LogicNode } from './strategyTypes';
|
|
import type { EdgeHandleBinding } from './strategyFlowLayout';
|
|
import { defaultStartMeta, type StartCombineOp, type StartNodeMeta } from './strategyStartNodes';
|
|
|
|
export type SignalFlowLayoutSnapshot = {
|
|
positions: Record<string, { x: number; y: number }>;
|
|
edgeHandles: Record<string, EdgeHandleBinding>;
|
|
orphans?: LogicNode[];
|
|
/** START 노드별 조건 판별 시간봉 */
|
|
startMeta?: Record<string, StartNodeMeta>;
|
|
/** 기본 START 외 추가 START 노드 ID (순서 유지) */
|
|
extraStartIds?: string[];
|
|
/** 추가 START에 연결된 서브트리 루트 */
|
|
extraRoots?: Record<string, LogicNode | null>;
|
|
/** START 2개 이상일 때 분기 결합 (AND | OR) */
|
|
startCombineOp?: StartCombineOp;
|
|
};
|
|
|
|
export type FlowLayoutChangePayload = Omit<SignalFlowLayoutSnapshot, 'orphans'> & {
|
|
tab: 'buy' | 'sell';
|
|
};
|
|
|
|
export type StrategyFlowLayoutStore = {
|
|
buy: SignalFlowLayoutSnapshot;
|
|
sell: SignalFlowLayoutSnapshot;
|
|
};
|
|
|
|
/** @deprecated 레거시 localStorage 키 — 신규 저장은 DB flow_layout_json만 사용 */
|
|
const LEGACY_STORAGE_KEY = 'gc_se_flow_layout_v1';
|
|
|
|
export function emptySignalFlowLayout(): SignalFlowLayoutSnapshot {
|
|
return {
|
|
positions: {},
|
|
edgeHandles: {},
|
|
orphans: [],
|
|
startMeta: defaultStartMeta(),
|
|
extraStartIds: [],
|
|
extraRoots: {},
|
|
};
|
|
}
|
|
|
|
export function emptyStrategyFlowLayout(): StrategyFlowLayoutStore {
|
|
return { buy: emptySignalFlowLayout(), sell: emptySignalFlowLayout() };
|
|
}
|
|
|
|
export function normalizeSignalFlowLayout(
|
|
snap: SignalFlowLayoutSnapshot | null | undefined,
|
|
): SignalFlowLayoutSnapshot {
|
|
if (!snap) return emptySignalFlowLayout();
|
|
return {
|
|
positions: snap.positions ?? {},
|
|
edgeHandles: snap.edgeHandles ?? {},
|
|
orphans: snap.orphans ?? [],
|
|
startMeta: snap.startMeta ?? defaultStartMeta(),
|
|
extraStartIds: snap.extraStartIds ?? [],
|
|
extraRoots: snap.extraRoots ?? {},
|
|
startCombineOp: snap.startCombineOp,
|
|
};
|
|
}
|
|
|
|
export function normalizeStrategyFlowLayout(
|
|
store: StrategyFlowLayoutStore | null | undefined,
|
|
): StrategyFlowLayoutStore | null {
|
|
if (!store) return null;
|
|
return {
|
|
buy: normalizeSignalFlowLayout(store.buy),
|
|
sell: normalizeSignalFlowLayout(store.sell),
|
|
};
|
|
}
|
|
|
|
export function buildStrategyFlowLayoutStore(
|
|
buy: Omit<SignalFlowLayoutSnapshot, 'orphans'>,
|
|
sell: Omit<SignalFlowLayoutSnapshot, 'orphans'>,
|
|
buyOrphans: LogicNode[],
|
|
sellOrphans: LogicNode[],
|
|
): StrategyFlowLayoutStore {
|
|
return {
|
|
buy: { ...buy, orphans: buyOrphans },
|
|
sell: { ...sell, orphans: sellOrphans },
|
|
};
|
|
}
|
|
|
|
/** DB에 flow layout이 없을 때만 1회 읽기 — 이후 저장은 서버로 이전 */
|
|
export function readLegacyFlowLayoutFromLocalStorage(
|
|
strategyKey: string,
|
|
): StrategyFlowLayoutStore | null {
|
|
try {
|
|
const raw = localStorage.getItem(LEGACY_STORAGE_KEY);
|
|
if (!raw) return null;
|
|
const all = JSON.parse(raw) as Record<string, StrategyFlowLayoutStore>;
|
|
return normalizeStrategyFlowLayout(all[strategyKey] ?? null);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function clearLegacyFlowLayoutFromLocalStorage(strategyKey: string): void {
|
|
try {
|
|
const raw = localStorage.getItem(LEGACY_STORAGE_KEY);
|
|
if (!raw) return;
|
|
const all = JSON.parse(raw) as Record<string, StrategyFlowLayoutStore>;
|
|
if (!(strategyKey in all)) return;
|
|
delete all[strategyKey];
|
|
if (Object.keys(all).length === 0) localStorage.removeItem(LEGACY_STORAGE_KEY);
|
|
else localStorage.setItem(LEGACY_STORAGE_KEY, JSON.stringify(all));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|