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:
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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']);
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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 (
|
||||
|
@ -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: () => { },
|
||||
},
|
||||
]);
|
||||
|
Reference in New Issue
Block a user