feat(pop): 컴포넌트 연결 시스템 구현 - 디자이너 설정 기반 검색->리스트 필터링
ConnectionEditor(연결 탭 UI) + useConnectionResolver(런타임 이벤트 라우터)를 추가하여 디자이너가 코드 없이 컴포넌트 간 데이터 흐름을 설정할 수 있도록 구현. pop-search -> pop-string-list 실시간 필터링(시나리오 2) 검증 완료. 주요 변경: - ConnectionEditor: 연결 추가/수정/삭제, 복수 컬럼 체크박스, 필터 모드 선택 - useConnectionResolver: connections 기반 __comp_output__/__comp_input__ 자동 라우팅 - connectionMeta 타입 + pop-search/pop-string-list에 sendable/receivable 등록 - PopDataConnection 확장 (sourceOutput, targetInput, filterConfig, targetColumns) - pop-search 개선: 필드명 자동화, set_value receivable, number 타입, DRY - pop-string-list: 복수 컬럼 OR 클라이언트 필터 수신 - "데이터" 탭 -> "연결" 탭, UI 용어 자연어화 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
68
frontend/hooks/pop/useConnectionResolver.ts
Normal file
68
frontend/hooks/pop/useConnectionResolver.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* useConnectionResolver - 런타임 컴포넌트 연결 해석기
|
||||
*
|
||||
* PopViewerWithModals에서 사용.
|
||||
* layout.dataFlow.connections를 읽고, 소스 컴포넌트의 __comp_output__ 이벤트를
|
||||
* 타겟 컴포넌트의 __comp_input__ 이벤트로 자동 변환/중계한다.
|
||||
*
|
||||
* 이벤트 규칙:
|
||||
* 소스: __comp_output__${sourceComponentId}__${outputKey}
|
||||
* 타겟: __comp_input__${targetComponentId}__${inputKey}
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { usePopEvent } from "./usePopEvent";
|
||||
import type { PopDataConnection } from "@/components/pop/designer/types/pop-layout";
|
||||
|
||||
interface UseConnectionResolverOptions {
|
||||
screenId: string;
|
||||
connections: PopDataConnection[];
|
||||
}
|
||||
|
||||
export function useConnectionResolver({
|
||||
screenId,
|
||||
connections,
|
||||
}: UseConnectionResolverOptions): void {
|
||||
const { publish, subscribe } = usePopEvent(screenId);
|
||||
|
||||
// 연결 목록을 ref로 저장하여 콜백 안정성 확보
|
||||
const connectionsRef = useRef(connections);
|
||||
connectionsRef.current = connections;
|
||||
|
||||
useEffect(() => {
|
||||
if (!connections || connections.length === 0) return;
|
||||
|
||||
const unsubscribers: (() => void)[] = [];
|
||||
|
||||
// 소스별로 그룹핑하여 구독 생성
|
||||
const sourceGroups = new Map<string, PopDataConnection[]>();
|
||||
for (const conn of connections) {
|
||||
const sourceEvent = `__comp_output__${conn.sourceComponent}__${conn.sourceOutput || conn.sourceField}`;
|
||||
const existing = sourceGroups.get(sourceEvent) || [];
|
||||
existing.push(conn);
|
||||
sourceGroups.set(sourceEvent, existing);
|
||||
}
|
||||
|
||||
for (const [sourceEvent, conns] of sourceGroups) {
|
||||
const unsub = subscribe(sourceEvent, (payload: unknown) => {
|
||||
for (const conn of conns) {
|
||||
const targetEvent = `__comp_input__${conn.targetComponent}__${conn.targetInput || conn.targetField}`;
|
||||
|
||||
// filterConfig가 있으면 payload에 첨부
|
||||
const enrichedPayload = conn.filterConfig
|
||||
? { value: payload, filterConfig: conn.filterConfig }
|
||||
: payload;
|
||||
|
||||
publish(targetEvent, enrichedPayload);
|
||||
}
|
||||
});
|
||||
unsubscribers.push(unsub);
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const unsub of unsubscribers) {
|
||||
unsub();
|
||||
}
|
||||
};
|
||||
}, [screenId, connections, subscribe, publish]);
|
||||
}
|
||||
Reference in New Issue
Block a user