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() {
createEffect(() => {
invoke<LedStripConfigContainer>('read_config').then((config) => {
console.log('App: read config', config);
setLedStripStore({
strips: config.strips,
mappers: config.mappers,
colorCalibration: config.color_calibration,
});
}).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 { 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 { ledStripStore } from '../../stores/led-strip.store';
import { Borders } from '../../constants/border';
import { LedType } from '../../models/led-strip-config';
import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
type LedCountControlItemProps = {
displayId: number;
@ -12,6 +13,8 @@ type LedCountControlItemProps = {
};
const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
const [stripConfiguration, { setHoveredStripPart }] = useContext(LedStripConfigurationContext);
const config = createMemo(() => {
return ledStripStore.strips.find(
(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 (
<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="text-center">
<span class="text-xs font-medium text-base-content">
@ -140,7 +163,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
type LedCountControlPanelProps = {
display: DisplayInfo;
} & JSX.HTMLAttributes<HTMLElement>;
} & JSX.HTMLAttributes<HTMLDivElement>;
export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props) => {
const [localProps, rootProps] = splitProps(props, ['display']);

View File

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

View File

@ -60,34 +60,16 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
);
if (index === -1) {
console.log('🔍 LED: Strip config not found', {
displayId: localProps.config.display_id,
border: localProps.config.border,
availableStrips: ledStripStore.strips.length
});
return;
}
const mapper = ledStripStore.mappers[index];
if (!mapper) {
console.log('🔍 LED: Mapper not found', { index, mappersCount: ledStripStore.mappers.length });
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;
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 index = offset + i * 3;
const r = ledStripStore.colors[index] || 0;
@ -96,12 +78,6 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
return `rgb(${r}, ${g}, ${b})`;
});
console.log('🎨 LED: Generated colors', {
border: localProps.config.border,
colorsCount: colors.length,
sampleColors: colors.slice(0, 3)
});
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 [dragStartIndex, setDragStartIndex] = 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);
let root: HTMLDivElement;
@ -38,9 +38,6 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
if (targetStart === props.mapper.start) {
return;
}
console.log(
`moving strip part ${props.strip.display_id} ${props.strip.border} from ${props.mapper.start} to ${targetStart}`,
);
invoke('move_strip_part', {
displayId: props.strip.display_id,
border: props.strip.border,
@ -151,6 +148,17 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
}).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 colors = ledStripStore.colors;
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)})`;
if (fullLeds.length <= fullIndex) {
console.error('out of range', fullIndex, fullLeds.length);
return;
}
@ -221,9 +228,16 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
return (
<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}
ondblclick={reverse}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
ref={root!}
>
<div

View File

@ -43,50 +43,35 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
const connectWebSocket = () => {
if (!isMounted) {
console.log('Component not mounted, skipping WebSocket connection');
return;
}
const wsUrl = `ws://127.0.0.1:8765`;
console.log('Connecting to WebSocket:', wsUrl, 'with displayId:', localProps.displayId);
setConnectionStatus('connecting');
websocket = new WebSocket(wsUrl);
websocket.binaryType = 'arraybuffer';
console.log('WebSocket object created:', websocket);
websocket.onopen = () => {
console.log('WebSocket connected successfully!');
setConnectionStatus('connected');
// Send initial configuration
const config = {
display_id: localProps.displayId,
width: localProps.width || 320, // Reduced from 400 for better performance
height: localProps.height || 180, // Reduced from 225 for better performance
quality: localProps.quality || 50 // Reduced from 75 for faster compression
width: localProps.width || 320,
height: localProps.height || 180,
quality: localProps.quality || 50
};
console.log('Sending WebSocket configuration:', config);
websocket?.send(JSON.stringify(config));
};
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) {
console.log('📦 Processing ArrayBuffer frame, size:', event.data.byteLength);
handleJpegFrame(new Uint8Array(event.data));
} else {
console.log('⚠️ Received non-ArrayBuffer data:', event.data);
}
};
websocket.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
setConnectionStatus('disconnected');
websocket = null;
@ -100,7 +85,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
setConnectionStatus('error');
};
};
@ -204,7 +188,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
// Initialize canvas and resize observer
onMount(() => {
console.log('ScreenViewWebSocket mounted with displayId:', localProps.displayId);
const context = canvas.getContext('2d');
setCtx(context);
@ -217,7 +200,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
resizeObserver.observe(root);
// Connect WebSocket
console.log('About to connect WebSocket...');
connectWebSocket();
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
const getStatusColor = () => {
@ -275,13 +247,6 @@ export const ScreenViewWebSocket: Component<ScreenViewWebSocketProps> = (props)
{connectionStatus() === 'connected' && (
<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>
{rootProps.children}

View File

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

View File

@ -7,9 +7,14 @@ export type LedStripConfigurationContextType = [
displayId: number;
border: Borders;
} | null;
hoveredStripPart: {
displayId: number;
border: Borders;
} | null;
},
{
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>([
{
selectedStripPart: null,
hoveredStripPart: null,
},
{
setSelectedStripPart: () => {},
setHoveredStripPart: () => { },
},
]);