mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 19:26:56 +00:00
a0bf5b5bf5
* feat(sidebar): bulk stack operations (select, start/stop/restart/update)
- Add ⊞ bulk mode toggle in SidebarActions (cyan active state, tooltip "Bulk
mode (B)"); keyboard shortcut B toggles, Esc exits, Ctrl+A selects all
visible (chip-filtered) stacks
- Reserved checkbox column in StackRow becomes visible and interactive in bulk
mode; clicking a row in bulk mode toggles selection instead of opening the
stack; kebab and context-menu still work in either mode
- SidebarBulkBar appears below filter chips when >=1 stack selected: shows
count, Start / Stop / Restart / Update actions; Update is disabled with a
Skipper TierBadge for Community licenses
- useBulkStackActions hook fans out operations via Promise.allSettled and
surfaces an aggregate toast ("3 of 4 restarted; 1 failed: plex")
- Bulk update enforced Skipper-gated frontend-side (isPaid check in hook) and
sends x-bulk-mode header for backend defense-in-depth
- Extract isInputFocused / isPaletteOpen to lib/keyboard-guards.ts; both
useStackKeyboardShortcuts and the new bulk keyboard effect now share the
same guards instead of duplicating the logic
- chipFilteredFiles captured via useRef in bulk keyboard effect so the listener
is not torn down and re-added on every status-poll cycle
* fix(sidebar): separate TooltipProviders for bulk and scan icon buttons
Wrapping both icon buttons in a single TooltipProvider made them
render as one flex child, collapsing the gap-2 between them.
Splitting into two independent TooltipProviders restores the 8px
gap and right padding of the scan button.
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
import { FolderSearch, Loader2, LayoutList } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface SidebarActionsProps {
|
|
createStackSlot: ReactNode;
|
|
onScan: () => void;
|
|
isScanning: boolean;
|
|
bulkMode: boolean;
|
|
onToggleBulkMode: () => void;
|
|
}
|
|
|
|
export function SidebarActions({ createStackSlot, onScan, isScanning, bulkMode, onToggleBulkMode }: SidebarActionsProps) {
|
|
return (
|
|
<div className="p-4 flex gap-2">
|
|
<div className="flex-1">{createStackSlot}</div>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className={cn('rounded-lg shrink-0 shadow-btn-glow', bulkMode && 'border-brand/40 text-brand bg-brand/10')}
|
|
onClick={onToggleBulkMode}
|
|
aria-pressed={bulkMode}
|
|
>
|
|
<LayoutList className="w-4 h-4" strokeWidth={1.5} />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
<p>Bulk mode (B)</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="rounded-lg shrink-0 shadow-btn-glow"
|
|
onClick={onScan}
|
|
disabled={isScanning}
|
|
>
|
|
{isScanning
|
|
? <Loader2 className="w-4 h-4 animate-spin" strokeWidth={1.5} />
|
|
: <FolderSearch className="w-4 h-4" strokeWidth={1.5} />}
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
<p>Scan stacks folder</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
);
|
|
}
|