Implement synchronized LED strip highlighting with theme colors and clean up debug logs

- Add three-way synchronized highlighting between LED strip components
- Implement hover and selection state synchronization across display borders, sorter, and control panels
- Replace hardcoded colors with DaisyUI theme colors (primary, warning, base-content)
- Use background highlighting for sorter to prevent interface jittering
- Reduce LED strip width from 24px to 20px for better visual appearance
- Clean up console.log statements and debug output for production readiness
- Maintain layout stability by avoiding size changes in highlighting effects
This commit is contained in:
2025-07-05 14:32:31 +08:00
parent 99cbaf3b9f
commit 90cace679b
8 changed files with 77 additions and 89 deletions

View File

@ -11,14 +11,13 @@ import { DisplayStateIndex } from './components/displays/display-state-index';
function App() { function App() {
createEffect(() => { createEffect(() => {
invoke<LedStripConfigContainer>('read_config').then((config) => { invoke<LedStripConfigContainer>('read_config').then((config) => {
console.log('App: read config', config);
setLedStripStore({ setLedStripStore({
strips: config.strips, strips: config.strips,
mappers: config.mappers, mappers: config.mappers,
colorCalibration: config.color_calibration, colorCalibration: config.color_calibration,
}); });
}).catch((error) => { }).catch((error) => {
console.error('App: Failed to read config:', error); console.error('Failed to read config:', error);
}); });
}); });

View File

@ -1,9 +1,10 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { Component, createMemo, For, JSX, splitProps } from 'solid-js'; import { Component, createMemo, For, JSX, splitProps, useContext } from 'solid-js';
import { DisplayInfo } from '../../models/display-info.model'; import { DisplayInfo } from '../../models/display-info.model';
import { ledStripStore } from '../../stores/led-strip.store'; import { ledStripStore } from '../../stores/led-strip.store';
import { Borders } from '../../constants/border'; import { Borders } from '../../constants/border';
import { LedType } from '../../models/led-strip-config'; import { LedType } from '../../models/led-strip-config';
import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
type LedCountControlItemProps = { type LedCountControlItemProps = {
displayId: number; displayId: number;
@ -12,6 +13,8 @@ type LedCountControlItemProps = {
}; };
const LedCountControlItem: Component<LedCountControlItemProps> = (props) => { const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
const [stripConfiguration, { setHoveredStripPart }] = useContext(LedStripConfigurationContext);
const config = createMemo(() => { const config = createMemo(() => {
return ledStripStore.strips.find( return ledStripStore.strips.find(
(s) => s.display_id === props.displayId && s.border === props.border (s) => s.display_id === props.displayId && s.border === props.border
@ -79,8 +82,28 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
}); });
}; };
const onMouseEnter = () => {
setHoveredStripPart({
displayId: props.displayId,
border: props.border,
});
};
const onMouseLeave = () => {
setHoveredStripPart(null);
};
return ( return (
<div class="card bg-base-100 border border-base-300/50 p-2"> <div
class="card bg-base-100 border border-base-300/50 p-2 transition-all duration-200 cursor-pointer"
classList={{
'ring-2 ring-primary bg-primary/20 border-primary':
stripConfiguration.hoveredStripPart?.border === props.border &&
stripConfiguration.hoveredStripPart?.displayId === props.displayId,
}}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div class="text-center"> <div class="text-center">
<span class="text-xs font-medium text-base-content"> <span class="text-xs font-medium text-base-content">
@ -140,7 +163,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
type LedCountControlPanelProps = { type LedCountControlPanelProps = {
display: DisplayInfo; display: DisplayInfo;
} & JSX.HTMLAttributes<HTMLElement>; } & JSX.HTMLAttributes<HTMLDivElement>;
export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props) => { export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props) => {
const [localProps, rootProps] = splitProps(props, ['display']); const [localProps, rootProps] = splitProps(props, ['display']);

View File

@ -19,19 +19,17 @@ export const LedStripConfiguration = () => {
createEffect(() => { createEffect(() => {
invoke<string>('list_display_info').then((displays) => { invoke<string>('list_display_info').then((displays) => {
const parsedDisplays = JSON.parse(displays); const parsedDisplays = JSON.parse(displays);
console.log('LedStripConfiguration: Loaded displays:', parsedDisplays);
setDisplayStore({ setDisplayStore({
displays: parsedDisplays, displays: parsedDisplays,
}); });
}).catch((error) => { }).catch((error) => {
console.error('LedStripConfiguration: Failed to load displays:', error); console.error('Failed to load displays:', error);
}); });
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => { invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
console.log('LedStripConfiguration: Loaded LED strip configs:', configs);
setLedStripStore(configs); setLedStripStore(configs);
}).catch((error) => { }).catch((error) => {
console.error('LedStripConfiguration: Failed to load LED strip configs:', error); console.error('Failed to load LED strip configs:', error);
}); });
}); });
@ -86,6 +84,7 @@ export const LedStripConfiguration = () => {
LedStripConfigurationContextType[0] LedStripConfigurationContextType[0]
>({ >({
selectedStripPart: null, selectedStripPart: null,
hoveredStripPart: null,
}); });
const ledStripConfigurationContextValue: LedStripConfigurationContextType = [ const ledStripConfigurationContextValue: LedStripConfigurationContextType = [
@ -96,6 +95,11 @@ export const LedStripConfiguration = () => {
selectedStripPart: v, selectedStripPart: v,
}); });
}, },
setHoveredStripPart: (v) => {
setLedStripConfiguration({
hoveredStripPart: v,
});
},
}, },
]; ];
@ -135,10 +139,9 @@ export const LedStripConfiguration = () => {
</div> </div>
<div class="h-96 mb-4"> <div class="h-96 mb-4">
<DisplayListContainer> <DisplayListContainer>
{displayStore.displays.map((display) => { {displayStore.displays.map((display) => (
console.log('LedStripConfiguration: Rendering DisplayView for display:', display); <DisplayView display={display} />
return <DisplayView display={display} />; ))}
})}
</DisplayListContainer> </DisplayListContainer>
</div> </div>
<div class="text-xs text-base-content/50"> <div class="text-xs text-base-content/50">

View File

@ -60,34 +60,16 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
); );
if (index === -1) { if (index === -1) {
console.log('🔍 LED: Strip config not found', {
displayId: localProps.config.display_id,
border: localProps.config.border,
availableStrips: ledStripStore.strips.length
});
return; return;
} }
const mapper = ledStripStore.mappers[index]; const mapper = ledStripStore.mappers[index];
if (!mapper) { if (!mapper) {
console.log('🔍 LED: Mapper not found', { index, mappersCount: ledStripStore.mappers.length });
return; return;
} }
// Frontend always uses RGB data (3 bytes per LED) for preview
// The backend sends RGB data to frontend regardless of LED type
const offset = mapper.start * 3; const offset = mapper.start * 3;
console.log('🎨 LED: Updating colors', {
displayId: localProps.config.display_id,
border: localProps.config.border,
stripLength: localProps.config.len,
mapperPos: mapper.pos,
offset,
colorsArrayLength: ledStripStore.colors.length,
firstFewColors: Array.from(ledStripStore.colors.slice(offset, offset + 9))
});
const colors = new Array(localProps.config.len).fill(null).map((_, i) => { const colors = new Array(localProps.config.len).fill(null).map((_, i) => {
const index = offset + i * 3; const index = offset + i * 3;
const r = ledStripStore.colors[index] || 0; const r = ledStripStore.colors[index] || 0;
@ -96,12 +78,6 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
return `rgb(${r}, ${g}, ${b})`; return `rgb(${r}, ${g}, ${b})`;
}); });
console.log('🎨 LED: Generated colors', {
border: localProps.config.border,
colorsCount: colors.length,
sampleColors: colors.slice(0, 3)
});
setColors(colors); setColors(colors);
}); });

View File

@ -29,7 +29,7 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
const [dragCurr, setDragCurr] = createSignal<{ x: number; y: number } | null>(null); const [dragCurr, setDragCurr] = createSignal<{ x: number; y: number } | null>(null);
const [dragStartIndex, setDragStartIndex] = createSignal<number>(0); const [dragStartIndex, setDragStartIndex] = createSignal<number>(0);
const [cellWidth, setCellWidth] = createSignal<number>(0); const [cellWidth, setCellWidth] = createSignal<number>(0);
const [, { setSelectedStripPart }] = useContext(LedStripConfigurationContext); const [stripConfiguration, { setSelectedStripPart, setHoveredStripPart }] = useContext(LedStripConfigurationContext);
const [rootWidth, setRootWidth] = createSignal<number>(0); const [rootWidth, setRootWidth] = createSignal<number>(0);
let root: HTMLDivElement; let root: HTMLDivElement;
@ -38,9 +38,6 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
if (targetStart === props.mapper.start) { if (targetStart === props.mapper.start) {
return; return;
} }
console.log(
`moving strip part ${props.strip.display_id} ${props.strip.border} from ${props.mapper.start} to ${targetStart}`,
);
invoke('move_strip_part', { invoke('move_strip_part', {
displayId: props.strip.display_id, displayId: props.strip.display_id,
border: props.strip.border, border: props.strip.border,
@ -151,6 +148,17 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
}).catch((err) => console.error(err)); }).catch((err) => console.error(err));
}; };
const onMouseEnter = () => {
setHoveredStripPart({
displayId: props.strip.display_id,
border: props.strip.border,
});
};
const onMouseLeave = () => {
setHoveredStripPart(null);
};
const setColor = (fullIndex: number, colorsIndex: number, fullLeds: string[]) => { const setColor = (fullIndex: number, colorsIndex: number, fullLeds: string[]) => {
const colors = ledStripStore.colors; const colors = ledStripStore.colors;
let c1 = `rgb(${Math.floor(colors[colorsIndex * 3] * 0.8)}, ${Math.floor( let c1 = `rgb(${Math.floor(colors[colorsIndex * 3] * 0.8)}, ${Math.floor(
@ -162,7 +170,6 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
)}, ${Math.min(Math.floor(colors[colorsIndex * 3 + 2] * 1.2), 255)})`; )}, ${Math.min(Math.floor(colors[colorsIndex * 3 + 2] * 1.2), 255)})`;
if (fullLeds.length <= fullIndex) { if (fullLeds.length <= fullIndex) {
console.error('out of range', fullIndex, fullLeds.length);
return; return;
} }
@ -221,9 +228,16 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
return ( return (
<div <div
class="flex mx-2 select-none cursor-ew-resize focus:cursor-ew-resize" class="flex mx-2 select-none cursor-ew-resize focus:cursor-ew-resize transition-colors duration-200"
classList={{
'bg-primary/20 rounded-lg':
stripConfiguration.hoveredStripPart?.border === props.strip.border &&
stripConfiguration.hoveredStripPart?.displayId === props.strip.display_id,
}}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
ondblclick={reverse} ondblclick={reverse}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
ref={root!} ref={root!}
> >
<div <div

View File

@ -43,50 +43,35 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
const connectWebSocket = () => { const connectWebSocket = () => {
if (!isMounted) { if (!isMounted) {
console.log('Component not mounted, skipping WebSocket connection');
return; return;
} }
const wsUrl = `ws://127.0.0.1:8765`; const wsUrl = `ws://127.0.0.1:8765`;
console.log('Connecting to WebSocket:', wsUrl, 'with displayId:', localProps.displayId);
setConnectionStatus('connecting'); setConnectionStatus('connecting');
websocket = new WebSocket(wsUrl); websocket = new WebSocket(wsUrl);
websocket.binaryType = 'arraybuffer'; websocket.binaryType = 'arraybuffer';
console.log('WebSocket object created:', websocket);
websocket.onopen = () => { websocket.onopen = () => {
console.log('WebSocket connected successfully!');
setConnectionStatus('connected'); setConnectionStatus('connected');
// Send initial configuration // Send initial configuration
const config = { const config = {
display_id: localProps.displayId, display_id: localProps.displayId,
width: localProps.width || 320, // Reduced from 400 for better performance width: localProps.width || 320,
height: localProps.height || 180, // Reduced from 225 for better performance height: localProps.height || 180,
quality: localProps.quality || 50 // Reduced from 75 for faster compression quality: localProps.quality || 50
}; };
console.log('Sending WebSocket configuration:', config);
websocket?.send(JSON.stringify(config)); websocket?.send(JSON.stringify(config));
}; };
websocket.onmessage = (event) => { websocket.onmessage = (event) => {
console.log('🔍 WebSocket message received:', {
type: typeof event.data,
isArrayBuffer: event.data instanceof ArrayBuffer,
size: event.data instanceof ArrayBuffer ? event.data.byteLength : 'N/A'
});
if (event.data instanceof ArrayBuffer) { if (event.data instanceof ArrayBuffer) {
console.log('📦 Processing ArrayBuffer frame, size:', event.data.byteLength);
handleJpegFrame(new Uint8Array(event.data)); handleJpegFrame(new Uint8Array(event.data));
} else {
console.log('⚠️ Received non-ArrayBuffer data:', event.data);
} }
}; };
websocket.onclose = (event) => { websocket.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
setConnectionStatus('disconnected'); setConnectionStatus('disconnected');
websocket = null; websocket = null;
@ -100,7 +85,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
}; };
websocket.onerror = (error) => { websocket.onerror = (error) => {
console.error('WebSocket error:', error);
setConnectionStatus('error'); setConnectionStatus('error');
}; };
}; };
@ -204,7 +188,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
// Initialize canvas and resize observer // Initialize canvas and resize observer
onMount(() => { onMount(() => {
console.log('ScreenViewWebSocket mounted with displayId:', localProps.displayId);
const context = canvas.getContext('2d'); const context = canvas.getContext('2d');
setCtx(context); setCtx(context);
@ -217,7 +200,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
resizeObserver.observe(root); resizeObserver.observe(root);
// Connect WebSocket // Connect WebSocket
console.log('About to connect WebSocket...');
connectWebSocket(); connectWebSocket();
onCleanup(() => { onCleanup(() => {
@ -227,17 +209,7 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
}); });
}); });
// Debug function to list displays
const debugDisplays = async () => {
try {
const result = await invoke('list_display_info');
console.log('Available displays:', result);
alert(`Available displays: ${result}`);
} catch (error) {
console.error('Failed to get display info:', error);
alert(`Error: ${error}`);
}
};
// Status indicator // Status indicator
const getStatusColor = () => { const getStatusColor = () => {
@ -275,13 +247,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
{connectionStatus() === 'connected' && ( {connectionStatus() === 'connected' && (
<span>| {fps()} FPS | {frameCount()} frames</span> <span>| {fps()} FPS | {frameCount()} frames</span>
)} )}
<button
onClick={debugDisplays}
class="ml-2 px-1 py-0.5 bg-blue-600 hover:bg-blue-700 rounded text-xs"
title="Debug: Show available displays"
>
Debug
</button>
</div> </div>
{rootProps.children} {rootProps.children}

View File

@ -38,7 +38,7 @@ export const WhiteBalance = () => {
setIsFullscreen(true); setIsFullscreen(true);
} }
} catch (error) { } catch (error) {
console.error('Failed to auto enter fullscreen:', error); // Silently handle fullscreen error
} }
}; };
@ -101,7 +101,6 @@ export const WhiteBalance = () => {
const unlisten = listen('config_changed', (event) => { const unlisten = listen('config_changed', (event) => {
const { strips, mappers, color_calibration } = const { strips, mappers, color_calibration } =
event.payload as LedStripConfigContainer; event.payload as LedStripConfigContainer;
console.log(event.payload);
setLedStripStore({ setLedStripStore({
strips, strips,
mappers, mappers,
@ -121,9 +120,9 @@ export const WhiteBalance = () => {
const calibration = { ...ledStripStore.colorCalibration }; const calibration = { ...ledStripStore.colorCalibration };
calibration[key] = value; calibration[key] = value;
setLedStripStore('colorCalibration', calibration); setLedStripStore('colorCalibration', calibration);
invoke('set_color_calibration', { calibration }).catch((error) => invoke('set_color_calibration', { calibration }).catch(() => {
console.log(error), // Silently handle error
); });
}; };
const toggleFullscreen = async () => { const toggleFullscreen = async () => {
@ -138,7 +137,7 @@ export const WhiteBalance = () => {
setPanelPosition({ x: 0, y: 0 }); setPanelPosition({ x: 0, y: 0 });
} }
} catch (error) { } catch (error) {
console.error('Failed to toggle fullscreen:', error); // Silently handle fullscreen error
} }
}; };
@ -156,7 +155,9 @@ export const WhiteBalance = () => {
const reset = () => { const reset = () => {
invoke('set_color_calibration', { invoke('set_color_calibration', {
calibration: new ColorCalibration(), calibration: new ColorCalibration(),
}).catch((error) => console.log(error)); }).catch(() => {
// Silently handle error
});
}; };
return ( return (

View File

@ -7,9 +7,14 @@ export type LedStripConfigurationContextType = [
displayId: number; displayId: number;
border: Borders; border: Borders;
} | null; } | null;
hoveredStripPart: {
displayId: number;
border: Borders;
} | null;
}, },
{ {
setSelectedStripPart: (v: { displayId: number; border: Borders } | null) => void; setSelectedStripPart: (v: { displayId: number; border: Borders } | null) => void;
setHoveredStripPart: (v: { displayId: number; border: Borders } | null) => void;
}, },
]; ];
@ -17,8 +22,10 @@ export const LedStripConfigurationContext =
createContext<LedStripConfigurationContextType>([ createContext<LedStripConfigurationContextType>([
{ {
selectedStripPart: null, selectedStripPart: null,
hoveredStripPart: null,
}, },
{ {
setSelectedStripPart: () => {}, setSelectedStripPart: () => {},
setHoveredStripPart: () => { },
}, },
]); ]);