import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { DiffControlsModel, useLiveControls, type ControlWithSource, } from '@teambit/compositions.ui.composition-live-controls'; import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton'; import { Icon } from '@teambit/evangelist.elements.icon'; import classNames from 'classnames'; import { getInputComponent } from './live-control-input'; import styles from './live-controls-diff-panel.module.scss'; type PanelStatus = 'loading' | 'available' | 'empty'; const WAIT_FOR_CONTROLS_MS = 1200; export type LiveControlsDiffPanelProps = { resetKey?: string; baseChannel?: string; compareChannel?: string; commonLabel?: string; baseLabel?: string; compareLabel?: string; showEmptyState?: boolean; onStatusChange?: (status: PanelStatus) => void; }; export function LiveControlsDiffPanel({ resetKey, baseChannel, compareChannel, commonLabel = 'Common', baseLabel = 'Base', compareLabel = 'Compare', showEmptyState = true, onStatusChange, }: LiveControlsDiffPanelProps) { const lastResetKeyRef = useRef(null); const [isWaitingForFreshData, setIsWaitingForFreshData] = useState(true); const waitTimeoutRef = useRef(null); const currentKey = `${baseChannel || ''}-${compareChannel || ''}-${resetKey || ''}`; const model = useMemo(() => new DiffControlsModel(baseChannel, compareChannel), [baseChannel, compareChannel]); const allChannels = useMemo(() => { const channels = [model.baseChannel, model.compareChannel]; if (!channels.includes('default')) channels.push('default'); return [...new Set(channels)]; }, [model.baseChannel, model.compareChannel]); const combined = useLiveControls(allChannels); const { ready: combinedReady, setTimestamp } = combined; useEffect(() => { if (lastResetKeyRef.current !== currentKey) { lastResetKeyRef.current = currentKey; setIsWaitingForFreshData(true); setTimestamp(0); } }, [currentKey, setTimestamp]); const channelsReady = Boolean(baseChannel && compareChannel); const registryReady = combinedReady || model.isReady; const controls = model.controls; const hasControls = controls.length > 0; const hasSubscribers = model.hasSubscribers; const prevRegistryReady = useRef(registryReady); useEffect(() => { if (registryReady && !prevRegistryReady.current && isWaitingForFreshData) { setIsWaitingForFreshData(false); } prevRegistryReady.current = registryReady; }, [registryReady, isWaitingForFreshData]); useEffect(() => { if (!channelsReady || registryReady || !isWaitingForFreshData) { if (waitTimeoutRef.current !== null) { window.clearTimeout(waitTimeoutRef.current); waitTimeoutRef.current = null; } return; } waitTimeoutRef.current = window.setTimeout(() => { setIsWaitingForFreshData(false); waitTimeoutRef.current = null; }, WAIT_FOR_CONTROLS_MS); return () => { if (waitTimeoutRef.current !== null) { window.clearTimeout(waitTimeoutRef.current); waitTimeoutRef.current = null; } }; }, [channelsReady, registryReady, isWaitingForFreshData, currentKey]); const status: PanelStatus = useMemo(() => { if (!channelsReady) return 'empty'; if (isWaitingForFreshData) return 'loading'; if (!registryReady && !hasSubscribers) return 'empty'; return hasControls ? 'available' : 'empty'; }, [channelsReady, isWaitingForFreshData, registryReady, hasSubscribers, hasControls]); useEffect(() => { onStatusChange?.(status); }, [status, onStatusChange]); const handleChange = useCallback( (control: ControlWithSource, value: any) => { model.updateControl(control.id, value, control.source); }, [model] ); if (status === 'loading') { return (
); } if (status === 'empty') { if (!showEmptyState) return null; return (
No live controls
This composition does not expose live controls.
); } const commonControls = controls.filter((c) => c.source === 'common'); const baseControls = controls.filter((c) => c.source === 'base'); const compareControls = controls.filter((c) => c.source === 'compare'); const hasBaseOrCompare = baseControls.length > 0 || compareControls.length > 0; const hasBaseAndCompare = baseControls.length > 0 && compareControls.length > 0; const getControlValue = (control: ControlWithSource) => { return model.getValueForControl(control.id, control.source); }; const renderControlList = (list: ControlWithSource[]) => ( ); return (
{commonControls.length > 0 && (
{commonLabel}
{renderControlList(commonControls)}
)} {commonControls.length > 0 && hasBaseOrCompare &&
} {baseControls.length > 0 && (
{baseLabel}
{renderControlList(baseControls)}
)} {hasBaseAndCompare &&
} {compareControls.length > 0 && (
{compareLabel}
{renderControlList(compareControls)}
)}
); }