5 Commits

Author SHA1 Message Date
4a3d7681d6 Fix layout spacing issues in LED strip configuration
- Remove forced height expansion from display configuration panel
- Change layout from flex-based to space-based for natural content sizing
- Eliminate large empty spaces between configuration sections
- Improve overall UI compactness and visual flow
2025-07-08 03:19:06 +08:00
2834b7fe57 Optimize responsive layout for LED count control interface
- Improve grid layout responsiveness from md:grid-cols-2 to lg:grid-cols-2
- Add responsive LED control items with grid-cols-2 sm:grid-cols-4
- Reduce padding and spacing for better small window display
- Optimize button and input sizes with compact styling
- Add custom CSS for small screen handling (<640px and <600px)
- Implement auto-fit grid layout with minmax(280px, 1fr)
- Enhance main container with overflow-x-auto and responsive padding
- Reduce overall page spacing and card body padding for compact display
2025-07-08 03:02:24 +08:00
c57f52ea74 Improve white balance panel drag functionality
- Fix drag event handling to only trigger on title bar area
- Prevent color slider interactions from triggering panel drag
- Add event propagation control for better user experience
- Improve cursor styling for interactive elements
2025-07-08 02:46:44 +08:00
9f37b4043c Fix device selection reset issue in LED Strip Testing
- Fix frontend device selection state management to properly update selected board when boards_changed event is triggered
- Optimize backend board status checking to only emit boards_changed events when there are actual changes
- Prevent unnecessary UI updates and improve performance

Fixes issue where device selection would reset to unselected state shortly after selection
2025-07-08 02:46:24 +08:00
92349eebb6 Fix hamburger menu button click issue
- Add dropdown-hover class for better interaction
- Add onClick handler to ensure proper focus management
- Increase z-index from z-[1] to z-[100] to prevent overlay issues
- Remove conflicting tabindex from menu items
- Add border and hover effects for better visual feedback
2025-07-07 19:20:05 +08:00
9 changed files with 151 additions and 89 deletions

View File

@@ -194,25 +194,42 @@ impl UdpRpc {
continue; continue;
} }
// Store previous board states to detect changes
let prev_boards = boards
.values()
.map(|it| async move { it.info.read().await.clone() });
let prev_boards = join_all(prev_boards).await;
// Check all boards
for board in boards.values() { for board in boards.values() {
if let Err(err) = board.check().await { if let Err(err) = board.check().await {
error!("failed to check board: {:?}", err); error!("failed to check board: {:?}", err);
} }
} }
let tx_boards = boards // Get current board states after check
let current_boards = boards
.values() .values()
.map(|it| async move { it.info.read().await.clone() }); .map(|it| async move { it.info.read().await.clone() });
let tx_boards = join_all(tx_boards).await; let current_boards = join_all(current_boards).await;
drop(boards); drop(boards);
let board_change_sender = self.boards_change_sender.clone(); // Only send update if there are actual changes
if let Err(err) = board_change_sender.send(tx_boards) { let has_changes = prev_boards.len() != current_boards.len() ||
error!("failed to send board change: {:?}", err); prev_boards.iter().zip(current_boards.iter()).any(|(prev, current)| {
} prev.connect_status != current.connect_status ||
prev.ttl != current.ttl ||
prev.checked_at != current.checked_at
});
drop(board_change_sender); if has_changes {
let board_change_sender = self.boards_change_sender.clone();
if let Err(err) = board_change_sender.send(current_boards) {
error!("failed to send board change: {:?}", err);
}
drop(board_change_sender);
}
} }
} }
} }

View File

@@ -46,22 +46,22 @@ function App() {
}); });
return ( return (
<div class="min-h-screen bg-base-100" data-theme="dark"> <div class="h-screen bg-base-100 flex flex-col" data-theme="dark">
{/* Fixed Navigation */} {/* Fixed Navigation */}
<div class="navbar bg-base-200 shadow-lg fixed top-0 left-0 right-0 z-50"> <div class="navbar bg-base-200 shadow-lg flex-shrink-0 z-50">
<div class="navbar-start"> <div class="navbar-start">
<div class="dropdown"> <div class="dropdown dropdown-hover">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden"> <div tabindex="0" role="button" class="btn btn-ghost lg:hidden" onClick={(e) => e.currentTarget.focus()}>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path>
</svg> </svg>
</div> </div>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"> <ul class="menu menu-sm dropdown-content mt-3 z-[100] p-2 shadow bg-base-100 rounded-box w-52 border border-base-300">
<li><A href="/info" class="text-base-content"></A></li> <li><A href="/info" class="text-base-content hover:bg-base-200"></A></li>
<li><A href="/displays" class="text-base-content"></A></li> <li><A href="/displays" class="text-base-content hover:bg-base-200"></A></li>
<li><A href="/led-strips-configuration" class="text-base-content"></A></li> <li><A href="/led-strips-configuration" class="text-base-content hover:bg-base-200"></A></li>
<li><A href="/white-balance" class="text-base-content"></A></li> <li><A href="/white-balance" class="text-base-content hover:bg-base-200"></A></li>
<li><A href="/led-strip-test" class="text-base-content"></A></li> <li><A href="/led-strip-test" class="text-base-content hover:bg-base-200"></A></li>
</ul> </ul>
</div> </div>
<a class="btn btn-ghost text-xl text-primary font-bold"></a> <a class="btn btn-ghost text-xl text-primary font-bold"></a>
@@ -80,8 +80,8 @@ function App() {
</div> </div>
</div> </div>
{/* Main Content with top padding to account for fixed navbar */} {/* Main Content - fills remaining height */}
<main class="container mx-auto p-4 pt-20"> <main class="flex-1 container mx-auto px-2 sm:px-4 py-4 max-w-full overflow-x-auto min-h-0">
<Routes> <Routes>
<Route path="/info" component={InfoIndex} /> <Route path="/info" component={InfoIndex} />
<Route path="/displays" component={DisplayStateIndex} /> <Route path="/displays" component={DisplayStateIndex} />

View File

@@ -75,7 +75,7 @@ export const DisplayListContainer: ParentComponent = (props) => {
createEffect(() => {}); createEffect(() => {});
return ( return (
<section ref={root!} class="relative bg-gray-400/30" style={rootStyle()}> <section ref={root!} class="relative bg-gray-400/30 h-full w-full" style={rootStyle()}>
<ol class="absolute" style={olStyle()}> <ol class="absolute" style={olStyle()}>
{props.children} {props.children}
</ol> </ol>

View File

@@ -95,7 +95,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
return ( return (
<div <div
class="card bg-base-100 border border-base-300/50 p-2 transition-all duration-200 cursor-pointer" class="card bg-base-100 border border-base-300/50 p-1.5 transition-all duration-200 cursor-pointer"
classList={{ classList={{
'ring-2 ring-primary bg-primary/20 border-primary': 'ring-2 ring-primary bg-primary/20 border-primary':
stripConfiguration.hoveredStripPart?.border === props.border && stripConfiguration.hoveredStripPart?.border === props.border &&
@@ -111,9 +111,9 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
</span> </span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-0.5">
<button <button
class="btn btn-xs btn-circle btn-outline flex-shrink-0" class="btn btn-xs btn-circle btn-outline flex-shrink-0 min-h-0 h-6 w-6"
onClick={handleDecrease} onClick={handleDecrease}
disabled={!config() || (config()?.len || 0) <= 0} disabled={!config() || (config()?.len || 0) <= 0}
title="减少LED数量" title="减少LED数量"
@@ -123,7 +123,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
<input <input
type="number" type="number"
class="input input-xs flex-1 text-center min-w-0 text-sm font-medium [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" class="input input-xs flex-1 text-center min-w-0 text-xs font-medium [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none h-6 px-1"
value={config()?.len || 0} value={config()?.len || 0}
min="0" min="0"
max="1000" max="1000"
@@ -136,7 +136,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
/> />
<button <button
class="btn btn-xs btn-circle btn-outline flex-shrink-0" class="btn btn-xs btn-circle btn-outline flex-shrink-0 min-h-0 h-6 w-6"
onClick={handleIncrease} onClick={handleIncrease}
disabled={!config() || (config()?.len || 0) >= 1000} disabled={!config() || (config()?.len || 0) >= 1000}
title="增加LED数量" title="增加LED数量"
@@ -147,7 +147,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
<div class="mt-1"> <div class="mt-1">
<select <select
class="select select-xs w-full text-xs" class="select select-xs w-full text-xs h-6 min-h-0"
value={config()?.led_type || LedType.RGB} value={config()?.led_type || LedType.RGB}
onChange={handleLedTypeChange} onChange={handleLedTypeChange}
title="LED类型" title="LED类型"
@@ -177,13 +177,13 @@ export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props
return ( return (
<div {...rootProps} class={'card bg-base-200 shadow-lg border border-base-300 ' + (rootProps.class || '')}> <div {...rootProps} class={'card bg-base-200 shadow-lg border border-base-300 ' + (rootProps.class || '')}>
<div class="card-body p-4"> <div class="card-body p-3">
<div class="card-title text-base mb-3 flex items-center justify-between"> <div class="card-title text-sm mb-2 flex items-center justify-between">
<span>LED数量控制</span> <span>LED数量控制</span>
<div class="badge badge-info badge-outline"> {localProps.display.id}</div> <div class="badge badge-info badge-outline text-xs"> {localProps.display.id}</div>
</div> </div>
<div class="grid grid-cols-4 gap-2"> <div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<For each={borders}> <For each={borders}>
{(item) => ( {(item) => (
<LedCountControlItem <LedCountControlItem
@@ -195,7 +195,7 @@ export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props
</For> </For>
</div> </div>
<div class="text-xs text-base-content/50 mt-3 p-2 bg-base-300/50 rounded"> <div class="text-xs text-base-content/50 mt-2 p-1.5 bg-base-300/50 rounded">
💡 +/- LED0-1000 💡 +/- LED0-1000
</div> </div>
</div> </div>

View File

@@ -104,62 +104,64 @@ export const LedStripConfiguration = () => {
]; ];
return ( return (
<div class="space-y-6"> <div class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-base-content"></h1> <h1 class="text-xl font-bold text-base-content"></h1>
<div class="stats shadow"> <div class="stats shadow">
<div class="stat"> <div class="stat py-2 px-4">
<div class="stat-title"></div> <div class="stat-title text-xs"></div>
<div class="stat-value text-primary">{displayStore.displays.length}</div> <div class="stat-value text-primary text-lg">{displayStore.displays.length}</div>
</div> </div>
</div> </div>
</div> </div>
<LedStripConfigurationContext.Provider value={ledStripConfigurationContextValue}> <LedStripConfigurationContext.Provider value={ledStripConfigurationContextValue}>
{/* LED Strip Sorter Panel */}
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-4">
<div class="card-title text-base mb-3">
<span></span>
<div class="badge badge-info badge-outline"></div>
</div>
<LedStripPartsSorter />
<div class="text-xs text-base-content/50 mt-2">
💡
</div>
</div>
</div>
{/* Display Configuration Panel */}
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-4">
<div class="card-title text-base mb-3">
<span></span>
<div class="badge badge-secondary badge-outline"></div>
</div>
<div class="h-96 mb-4">
<DisplayListContainer>
{displayStore.displays.map((display) => (
<DisplayView display={display} />
))}
</DisplayListContainer>
</div>
<div class="text-xs text-base-content/50">
💡 使LED数量
</div>
</div>
</div>
{/* LED Count Control Panels */}
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center gap-2 mb-3"> {/* LED Strip Sorter Panel */}
<h2 class="text-lg font-semibold text-base-content">LED数量控制</h2> <div class="card bg-base-200 shadow-lg">
<div class="badge badge-info badge-outline"></div> <div class="card-body p-3">
<div class="card-title text-sm mb-2">
<span></span>
<div class="badge badge-info badge-outline text-xs"></div>
</div>
<LedStripPartsSorter />
<div class="text-xs text-base-content/50 mt-2">
💡
</div>
</div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{displayStore.displays.map((display) => ( {/* Display Configuration Panel - Auto height based on content */}
<LedCountControlPanel display={display} /> <div class="card bg-base-200 shadow-lg">
))} <div class="card-body p-3">
<div class="card-title text-sm mb-2">
<span></span>
<div class="badge badge-secondary badge-outline text-xs"></div>
</div>
<div class="mb-3">
<DisplayListContainer>
{displayStore.displays.map((display) => (
<DisplayView display={display} />
))}
</DisplayListContainer>
</div>
<div class="text-xs text-base-content/50">
💡 使LED数量
</div>
</div>
</div>
{/* LED Count Control Panels */}
<div class="flex-shrink-0">
<div class="flex items-center gap-2 mb-2">
<h2 class="text-base font-semibold text-base-content">LED数量控制</h2>
<div class="badge badge-info badge-outline text-xs"></div>
</div>
<div class="led-control-grid">
{displayStore.displays.map((display) => (
<LedCountControlPanel display={display} />
))}
</div>
</div> </div>
</div> </div>
</LedStripConfigurationContext.Provider> </LedStripConfigurationContext.Provider>

View File

@@ -58,7 +58,10 @@ export const LedStripTest = () => {
board.port === currentBoard.port board.port === currentBoard.port
); );
if (!stillExists) { if (stillExists) {
// Update to the new board object to reflect any status changes
setSelectedBoard(stillExists);
} else {
// Current board is no longer available, select first available or null // Current board is no longer available, select first available or null
setSelectedBoard(boardList.length > 0 ? boardList[0] : null); setSelectedBoard(boardList.length > 0 ? boardList[0] : null);
} }

View File

@@ -5,6 +5,16 @@ type Props = {
} & JSX.HTMLAttributes<HTMLInputElement>; } & JSX.HTMLAttributes<HTMLInputElement>;
export const ColorSlider: Component<Props> = (props) => { export const ColorSlider: Component<Props> = (props) => {
const handleMouseDown = (e: MouseEvent) => {
// 阻止事件冒泡到父元素,避免触发面板拖拽
e.stopPropagation();
};
const handleMouseMove = (e: MouseEvent) => {
// 阻止事件冒泡到父元素
e.stopPropagation();
};
return ( return (
<input <input
type="range" type="range"
@@ -17,6 +27,8 @@ export const ColorSlider: Component<Props> = (props) => {
'range range-primary w-full bg-gradient-to-r ' + 'range range-primary w-full bg-gradient-to-r ' +
props.class props.class
} }
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
/> />
); );
}; };

View File

@@ -56,13 +56,17 @@ export const WhiteBalance = () => {
// 拖拽处理函数 // 拖拽处理函数
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
// 确保只有在标题栏区域点击时才触发拖拽
setIsDragging(true); setIsDragging(true);
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const panelRect = (e.currentTarget as HTMLElement).closest('.fixed')?.getBoundingClientRect();
setDragOffset({ if (panelRect) {
x: e.clientX - rect.left, setDragOffset({
y: e.clientY - rect.top x: e.clientX - panelRect.left,
}); y: e.clientY - panelRect.top
});
}
e.preventDefault(); e.preventDefault();
e.stopPropagation();
}; };
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
@@ -338,22 +342,24 @@ export const WhiteBalance = () => {
{/* 可拖拽的RGB控制面板 */} {/* 可拖拽的RGB控制面板 */}
<div <div
class="fixed w-80 bg-base-200/95 backdrop-blur-sm rounded-lg shadow-xl z-60 cursor-move select-none" class="fixed w-80 bg-base-200/95 backdrop-blur-sm rounded-lg shadow-xl z-60 select-none"
style={{ style={{
left: `${panelPosition().x}px`, left: `${panelPosition().x}px`,
top: `${panelPosition().y}px`, top: `${panelPosition().y}px`,
transform: 'none' transform: 'none'
}} }}
onMouseDown={handleMouseDown}
> >
<div class="card-body p-4"> <div class="card-body p-4">
<div class="card-title text-base mb-3 flex justify-between items-center"> <div
class="card-title text-base mb-3 flex justify-between items-center cursor-move"
onMouseDown={handleMouseDown}
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs opacity-60"></span> <span class="text-xs opacity-60"></span>
<span>RGB调节</span> <span>RGB调节</span>
<div class="badge badge-secondary badge-outline"></div> <div class="badge badge-secondary badge-outline"></div>
</div> </div>
<button class="btn btn-ghost btn-xs" onClick={toggleFullscreen} title="退出全屏"> <button class="btn btn-ghost btn-xs cursor-pointer" onClick={toggleFullscreen} title="退出全屏">
<BsFullscreenExit size={14} /> <BsFullscreenExit size={14} />
</button> </button>
</div> </div>

View File

@@ -1,2 +1,24 @@
@import "tailwindcss"; @import "tailwindcss";
@config "../tailwind.config.js"; @config "../tailwind.config.js";
/* Custom responsive styles for small windows */
@media (max-width: 640px) {
.container {
max-width: 100%;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
}
/* Ensure LED control panels are responsive */
.led-control-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 0.75rem;
}
@media (max-width: 600px) {
.led-control-grid {
grid-template-columns: 1fr;
}
}