127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
/**
|
|
* useConnectionResolver - 런타임 컴포넌트 연결 해석기
|
|
*
|
|
* PopViewerWithModals에서 사용.
|
|
* layout.dataFlow.connections를 읽고, 소스 컴포넌트의 __comp_output__ 이벤트를
|
|
* 타겟 컴포넌트의 __comp_input__ 이벤트로 자동 변환/중계한다.
|
|
*
|
|
* 이벤트 규칙:
|
|
* 소스: __comp_output__${sourceComponentId}__${outputKey}
|
|
* 타겟: __comp_input__${targetComponentId}__${inputKey}
|
|
*
|
|
* _auto 모드:
|
|
* sourceOutput="_auto"인 연결은 소스/타겟의 connectionMeta를 비교하여
|
|
* key가 같고 category="event"인 쌍을 모두 자동 라우팅한다.
|
|
*/
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { usePopEvent } from "./usePopEvent";
|
|
import type { PopDataConnection } from "@/components/pop/designer/types/pop-layout";
|
|
import {
|
|
PopComponentRegistry,
|
|
type ConnectionMetaItem,
|
|
} from "@/lib/registry/PopComponentRegistry";
|
|
|
|
interface UseConnectionResolverOptions {
|
|
screenId: string;
|
|
connections: PopDataConnection[];
|
|
componentTypes?: Map<string, string>;
|
|
}
|
|
|
|
/**
|
|
* 소스/타겟의 connectionMeta에서 자동 매칭 가능한 이벤트 쌍을 찾는다.
|
|
* 규칙: category="event"이고 key가 동일한 쌍
|
|
*/
|
|
function getAutoMatchPairs(
|
|
sourceType: string,
|
|
targetType: string
|
|
): { sourceKey: string; targetKey: string }[] {
|
|
const sourceDef = PopComponentRegistry.getComponent(sourceType);
|
|
const targetDef = PopComponentRegistry.getComponent(targetType);
|
|
|
|
if (!sourceDef?.connectionMeta?.sendable || !targetDef?.connectionMeta?.receivable) {
|
|
return [];
|
|
}
|
|
|
|
const pairs: { sourceKey: string; targetKey: string }[] = [];
|
|
|
|
for (const s of sourceDef.connectionMeta.sendable) {
|
|
if (s.category !== "event") continue;
|
|
for (const r of targetDef.connectionMeta.receivable) {
|
|
if (r.category !== "event") continue;
|
|
if (s.key === r.key) {
|
|
pairs.push({ sourceKey: s.key, targetKey: r.key });
|
|
}
|
|
}
|
|
}
|
|
|
|
return pairs;
|
|
}
|
|
|
|
export function useConnectionResolver({
|
|
screenId,
|
|
connections,
|
|
componentTypes,
|
|
}: UseConnectionResolverOptions): void {
|
|
const { publish, subscribe } = usePopEvent(screenId);
|
|
|
|
const connectionsRef = useRef(connections);
|
|
connectionsRef.current = connections;
|
|
|
|
const componentTypesRef = useRef(componentTypes);
|
|
componentTypesRef.current = componentTypes;
|
|
|
|
useEffect(() => {
|
|
if (!connections || connections.length === 0) return;
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
for (const conn of connections) {
|
|
const isAutoMode = conn.sourceOutput === "_auto" || !conn.sourceOutput;
|
|
|
|
if (isAutoMode && componentTypesRef.current) {
|
|
const sourceType = componentTypesRef.current.get(conn.sourceComponent);
|
|
const targetType = componentTypesRef.current.get(conn.targetComponent);
|
|
|
|
if (!sourceType || !targetType) continue;
|
|
|
|
const pairs = getAutoMatchPairs(sourceType, targetType);
|
|
|
|
for (const pair of pairs) {
|
|
const sourceEvent = `__comp_output__${conn.sourceComponent}__${pair.sourceKey}`;
|
|
const targetEvent = `__comp_input__${conn.targetComponent}__${pair.targetKey}`;
|
|
|
|
const unsub = subscribe(sourceEvent, (payload: unknown) => {
|
|
publish(targetEvent, {
|
|
value: payload,
|
|
_connectionId: conn.id,
|
|
});
|
|
});
|
|
unsubscribers.push(unsub);
|
|
}
|
|
} else {
|
|
const sourceEvent = `__comp_output__${conn.sourceComponent}__${conn.sourceOutput || conn.sourceField}`;
|
|
|
|
const unsub = subscribe(sourceEvent, (payload: unknown) => {
|
|
const targetEvent = `__comp_input__${conn.targetComponent}__${conn.targetInput || conn.targetField}`;
|
|
|
|
const enrichedPayload = {
|
|
value: payload,
|
|
filterConfig: conn.filterConfig,
|
|
_connectionId: conn.id,
|
|
};
|
|
|
|
publish(targetEvent, enrichedPayload);
|
|
});
|
|
unsubscribers.push(unsub);
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
for (const unsub of unsubscribers) {
|
|
unsub();
|
|
}
|
|
};
|
|
}, [screenId, connections, subscribe, publish]);
|
|
}
|