feat: 时间分布和详细时间点滑块横屏下左右分布
This commit is contained in:
parent
597d8e5d67
commit
5fa9d39c2f
@ -800,260 +800,264 @@ export default function HostDetail() {
|
|||||||
|
|
||||||
{/* 截图时间线选项卡 */}
|
{/* 截图时间线选项卡 */}
|
||||||
{activeTab === 'screenshots' && (
|
{activeTab === 'screenshots' && (
|
||||||
<div>
|
<div className="lg:flex lg:gap-6 items-start">
|
||||||
{/* 小时分布滑块(时间分布) */}
|
<div className="lg:w-1/3 lg:flex-shrink-0 space-y-8">
|
||||||
<div className="mb-8">
|
{/* 小时分布滑块(时间分布) */}
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div>
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white">时间分布</h2>
|
<div className="flex justify-between items-center mb-4">
|
||||||
<button
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white">时间分布</h2>
|
||||||
onClick={fetchTimeDistribution}
|
<button
|
||||||
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
|
onClick={fetchTimeDistribution}
|
||||||
>
|
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
<RefreshCcw className={`h-5 w-5 text-gray-600 dark:text-gray-400 ${loadingDistribution ? 'animate-spin' : ''}`} />
|
>
|
||||||
</button>
|
<RefreshCcw className={`h-5 w-5 text-gray-600 dark:text-gray-400 ${loadingDistribution ? 'animate-spin' : ''}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RecordDatePicker
|
||||||
|
value={selectedDate}
|
||||||
|
dailyCounts={dailyCounts}
|
||||||
|
onChange={setSelectedDate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hourlySegments.length > 0 ? (
|
||||||
|
<div className="mt-4">
|
||||||
|
<TimelineSlider
|
||||||
|
minTime={hourlyMinTime}
|
||||||
|
maxTime={hourlyMaxTime}
|
||||||
|
value={hourlySliderValue}
|
||||||
|
mode="segments"
|
||||||
|
segments={hourlySegments}
|
||||||
|
onChange={onHourlySliderChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-gray-500 dark:text-gray-400 mt-4">加载时间分布中...</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RecordDatePicker
|
{/* 详细时间点滑块 */}
|
||||||
value={selectedDate}
|
{showDetailTimeline && (
|
||||||
dailyCounts={dailyCounts}
|
<div>
|
||||||
onChange={setSelectedDate}
|
<div className="flex justify-between items-center mb-4">
|
||||||
/>
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white">时间点详情</h2>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={generateVideo}
|
||||||
|
disabled={generatingVideo}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white text-sm rounded focus:outline-none transition-colors"
|
||||||
|
title="生成视频"
|
||||||
|
>
|
||||||
|
{generatingVideo ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Video className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
{generatingVideo ? '生成中...' : '生成视频'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const selectedSec = Math.floor(hourlySliderValue / 3600000) * 3600;
|
||||||
|
fetchHourlyRecords(selectedSec, selectedSec + 3600);
|
||||||
|
}}
|
||||||
|
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
|
title="刷新"
|
||||||
|
>
|
||||||
|
<RefreshCcw className={`h-5 w-5 text-gray-600 dark:text-gray-400 ${loadingRecords ? 'animate-spin' : ''}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{hourlySegments.length > 0 ? (
|
{records.length > 0 ? (
|
||||||
<div className="mt-4">
|
<TimelineSlider
|
||||||
<TimelineSlider
|
minTime={timeRange.min}
|
||||||
minTime={hourlyMinTime}
|
maxTime={timeRange.max}
|
||||||
maxTime={hourlyMaxTime}
|
value={detailedSliderValue}
|
||||||
value={hourlySliderValue}
|
mode="ticks"
|
||||||
mode="segments"
|
markers={detailedMarkers}
|
||||||
segments={hourlySegments}
|
onChange={onDetailedSliderChange}
|
||||||
onChange={onHourlySliderChange}
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<div className="text-gray-500 dark:text-gray-400">加载记录中...</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="text-gray-500 dark:text-gray-400 mt-4">加载时间分布中...</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 详细时间点滑块 */}
|
|
||||||
{showDetailTimeline && (
|
|
||||||
<div className="mb-8">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white">时间点详情</h2>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
onClick={generateVideo}
|
|
||||||
disabled={generatingVideo}
|
|
||||||
className="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white text-sm rounded focus:outline-none transition-colors"
|
|
||||||
title="生成视频"
|
|
||||||
>
|
|
||||||
{generatingVideo ? (
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Video className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
{generatingVideo ? '生成中...' : '生成视频'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const selectedSec = Math.floor(hourlySliderValue / 3600000) * 3600;
|
|
||||||
fetchHourlyRecords(selectedSec, selectedSec + 3600);
|
|
||||||
}}
|
|
||||||
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
||||||
title="刷新"
|
|
||||||
>
|
|
||||||
<RefreshCcw className={`h-5 w-5 text-gray-600 dark:text-gray-400 ${loadingRecords ? 'animate-spin' : ''}`} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{records.length > 0 ? (
|
|
||||||
<TimelineSlider
|
|
||||||
minTime={timeRange.min}
|
|
||||||
maxTime={timeRange.max}
|
|
||||||
value={detailedSliderValue}
|
|
||||||
mode="ticks"
|
|
||||||
markers={detailedMarkers}
|
|
||||||
onChange={onDetailedSliderChange}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="text-gray-500 dark:text-gray-400">加载记录中...</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 图片预览区域及控制按钮 */}
|
{/* 图片预览区域及控制按钮 */}
|
||||||
{selectedRecord && (
|
<div className="lg:flex-1 min-w-0 mt-8 lg:mt-0">
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
|
{selectedRecord && (
|
||||||
{
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
|
||||||
!(navigator as any).connection?.saveData &&
|
{
|
||||||
records.map(record => record.screenshots).flat()
|
!(navigator as any).connection?.saveData &&
|
||||||
.filter((_, index) => Math.abs(index - currentFrameIndex) <= 20)
|
records.map(record => record.screenshots).flat()
|
||||||
.map(screenshot => <link rel="preload" key={screenshot.fileId} href={`/screenshots/${screenshot.fileId}`} as="image" />)
|
.filter((_, index) => Math.abs(index - currentFrameIndex) <= 20)
|
||||||
}
|
.map(screenshot => <link rel="preload" key={screenshot.fileId} href={`/screenshots/${screenshot.fileId}`} as="image" />)
|
||||||
{/* 图片预览区域 */}
|
}
|
||||||
{selectedRecord.screenshots.map((screenshot, sIndex) => (
|
{/* 图片预览区域 */}
|
||||||
<div key={sIndex} className="relative mb-6">
|
{selectedRecord.screenshots.map((screenshot, sIndex) => (
|
||||||
<div
|
<div key={sIndex} className="relative mb-6">
|
||||||
className="relative w-full"
|
<div
|
||||||
style={{ aspectRatio: imageAspectRatio }}
|
className="relative w-full"
|
||||||
>
|
style={{ aspectRatio: imageAspectRatio }}
|
||||||
<img
|
>
|
||||||
src={`/screenshots/${screenshot.fileId}`}
|
<img
|
||||||
alt={screenshot.monitorName}
|
src={`/screenshots/${screenshot.fileId}`}
|
||||||
className="absolute top-0 left-0 w-full h-full object-contain shadow-sm hover:shadow-md transition-shadow"
|
alt={screenshot.monitorName}
|
||||||
onLoad={(e) => onImageLoad(e, screenshot.fileId)}
|
className="absolute top-0 left-0 w-full h-full object-contain shadow-sm hover:shadow-md transition-shadow"
|
||||||
onError={() => onImageError(screenshot.fileId)}
|
onLoad={(e) => onImageLoad(e, screenshot.fileId)}
|
||||||
/>
|
onError={() => onImageError(screenshot.fileId)}
|
||||||
{loadingImageIds.has(screenshot.fileId) && (
|
|
||||||
<div className="absolute top-2 right-2 bg-black/40 rounded-full p-2">
|
|
||||||
<Loader2 className="h-5 w-5 text-white animate-spin" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 图片说明 */}
|
|
||||||
<div className="absolute bottom-4 left-4 bg-black/40 dark:bg-gray-900/40 text-white px-2 py-1 rounded">
|
|
||||||
<div className="text-sm">{screenshot.monitorName}</div>
|
|
||||||
<div className="text-xs">
|
|
||||||
{new Date(selectedRecord.timestamp).toLocaleString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 左侧点击区域 - 上一帧 */}
|
|
||||||
<div
|
|
||||||
onPointerDown={(e) => { e.preventDefault(); prevFrame(); }}
|
|
||||||
onTouchStart={(e) => e.preventDefault()}
|
|
||||||
className="absolute bottom-0 left-0 h-full w-1/3 text-white px-2 py-1 cursor-pointer"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 右侧点击区域 - 下一帧 */}
|
|
||||||
<div
|
|
||||||
onPointerDown={(e) => { e.preventDefault(); nextFrame(); }}
|
|
||||||
onTouchStart={(e) => e.preventDefault()}
|
|
||||||
className="absolute bottom-0 right-0 h-full w-1/3 text-white px-2 py-1 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* 控制按钮区域 */}
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-4 mb-6 backdrop-blur-sm border border-gray-200/50 dark:border-gray-600/50">
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
||||||
|
|
||||||
{/* 速度控制 */}
|
|
||||||
<div className="flex items-center gap-3 bg-white dark:bg-gray-800 rounded-lg px-4 py-2.5 shadow-sm border border-gray-200 dark:border-gray-600">
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 min-w-max">播放速度</span>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={autoPlaySpeed}
|
|
||||||
onChange={(e) => setAutoPlaySpeed(Number(e.target.value))}
|
|
||||||
className="w-20 text-sm px-3 py-1.5 border border-gray-300 dark:border-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition-colors"
|
|
||||||
min="0"
|
|
||||||
max="2000"
|
|
||||||
step="50"
|
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400 min-w-max">毫秒</span>
|
{loadingImageIds.has(screenshot.fileId) && (
|
||||||
|
<div className="absolute top-2 right-2 bg-black/40 rounded-full p-2">
|
||||||
|
<Loader2 className="h-5 w-5 text-white animate-spin" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 图片说明 */}
|
||||||
|
<div className="absolute bottom-4 left-4 bg-black/40 dark:bg-gray-900/40 text-white px-2 py-1 rounded">
|
||||||
|
<div className="text-sm">{screenshot.monitorName}</div>
|
||||||
|
<div className="text-xs">
|
||||||
|
{new Date(selectedRecord.timestamp).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 左侧点击区域 - 上一帧 */}
|
||||||
|
<div
|
||||||
|
onPointerDown={(e) => { e.preventDefault(); prevFrame(); }}
|
||||||
|
onTouchStart={(e) => e.preventDefault()}
|
||||||
|
className="absolute bottom-0 left-0 h-full w-1/3 text-white px-2 py-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 右侧点击区域 - 下一帧 */}
|
||||||
|
<div
|
||||||
|
onPointerDown={(e) => { e.preventDefault(); nextFrame(); }}
|
||||||
|
onTouchStart={(e) => e.preventDefault()}
|
||||||
|
className="absolute bottom-0 right-0 h-full w-1/3 text-white px-2 py-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 控制按钮区域 */}
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-4 mb-6 backdrop-blur-sm border border-gray-200/50 dark:border-gray-600/50">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
|
|
||||||
|
{/* 速度控制 */}
|
||||||
|
<div className="flex items-center gap-3 bg-white dark:bg-gray-800 rounded-lg px-4 py-2.5 shadow-sm border border-gray-200 dark:border-gray-600">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 min-w-max">播放速度</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={autoPlaySpeed}
|
||||||
|
onChange={(e) => setAutoPlaySpeed(Number(e.target.value))}
|
||||||
|
className="w-20 text-sm px-3 py-1.5 border border-gray-300 dark:border-gray-500 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition-colors"
|
||||||
|
min="0"
|
||||||
|
max="2000"
|
||||||
|
step="50"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-gray-500 dark:text-gray-400 min-w-max">毫秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 播放控制按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={toggleAutoPlay}
|
||||||
|
className={`flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium text-sm transition-all duration-200 shadow-sm ${autoPlay
|
||||||
|
? 'bg-red-500 hover:bg-red-600 text-white shadow-red-500/25'
|
||||||
|
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-blue-500/25'
|
||||||
|
} hover:shadow-lg hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 ${autoPlay ? 'focus:ring-red-500' : 'focus:ring-blue-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{!autoPlay ? (
|
||||||
|
<>
|
||||||
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
播放
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
暂停
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 星标按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={() => toggleRecordStar(selectedRecord.id)}
|
||||||
|
disabled={updatingStars.has(selectedRecord.id)}
|
||||||
|
className={`flex items-center gap-2 px-4 py-2.5 rounded-lg font-medium text-sm transition-all duration-200 shadow-sm border ${selectedRecord.isStarred
|
||||||
|
? 'bg-yellow-50 hover:bg-yellow-100 dark:bg-yellow-900/20 dark:hover:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 shadow-yellow-500/20'
|
||||||
|
: 'bg-white hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600 hover:text-yellow-600 dark:hover:text-yellow-400'
|
||||||
|
} ${updatingStars.has(selectedRecord.id)
|
||||||
|
? 'opacity-50 cursor-not-allowed'
|
||||||
|
: 'hover:scale-105 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500'
|
||||||
|
}`}
|
||||||
|
title={selectedRecord.isStarred ? '取消星标' : '添加星标'}
|
||||||
|
>
|
||||||
|
{updatingStars.has(selectedRecord.id) ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
处理中
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Star className={`h-4 w-4 ${selectedRecord.isStarred ? 'fill-current' : ''}`} />
|
||||||
|
{selectedRecord.isStarred ? '已收藏' : '收藏'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 快捷键提示 */}
|
||||||
|
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600">
|
||||||
|
<div className="flex flex-wrap justify-center gap-4 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">空格</kbd>
|
||||||
|
播放/暂停
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">←/→</kbd>
|
||||||
|
上一帧/下一帧
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">S</kbd>
|
||||||
|
切换星标
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 播放控制按钮 */}
|
|
||||||
<button
|
|
||||||
onClick={toggleAutoPlay}
|
|
||||||
className={`flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium text-sm transition-all duration-200 shadow-sm ${autoPlay
|
|
||||||
? 'bg-red-500 hover:bg-red-600 text-white shadow-red-500/25'
|
|
||||||
: 'bg-blue-500 hover:bg-blue-600 text-white shadow-blue-500/25'
|
|
||||||
} hover:shadow-lg hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 ${autoPlay ? 'focus:ring-red-500' : 'focus:ring-blue-500'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{!autoPlay ? (
|
|
||||||
<>
|
|
||||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clipRule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
播放
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
暂停
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 星标按钮 */}
|
|
||||||
<button
|
|
||||||
onClick={() => toggleRecordStar(selectedRecord.id)}
|
|
||||||
disabled={updatingStars.has(selectedRecord.id)}
|
|
||||||
className={`flex items-center gap-2 px-4 py-2.5 rounded-lg font-medium text-sm transition-all duration-200 shadow-sm border ${selectedRecord.isStarred
|
|
||||||
? 'bg-yellow-50 hover:bg-yellow-100 dark:bg-yellow-900/20 dark:hover:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 shadow-yellow-500/20'
|
|
||||||
: 'bg-white hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600 hover:text-yellow-600 dark:hover:text-yellow-400'
|
|
||||||
} ${updatingStars.has(selectedRecord.id)
|
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: 'hover:scale-105 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500'
|
|
||||||
}`}
|
|
||||||
title={selectedRecord.isStarred ? '取消星标' : '添加星标'}
|
|
||||||
>
|
|
||||||
{updatingStars.has(selectedRecord.id) ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
处理中
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Star className={`h-4 w-4 ${selectedRecord.isStarred ? 'fill-current' : ''}`} />
|
|
||||||
{selectedRecord.isStarred ? '已收藏' : '收藏'}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 快捷键提示 */}
|
{/* 窗口信息 */}
|
||||||
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600">
|
<div className="w-full">
|
||||||
<div className="flex flex-wrap justify-center gap-4 text-xs text-gray-500 dark:text-gray-400">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-3">活动窗口</h3>
|
||||||
<span className="flex items-center gap-1">
|
<div className="bg-gray-50 dark:bg-gray-700 rounded-md p-4">
|
||||||
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">空格</kbd>
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||||
播放/暂停
|
{selectedRecord.windows.map((window, index) => (
|
||||||
</span>
|
<div key={index} className="p-4 bg-white dark:bg-gray-800 rounded-md shadow-sm">
|
||||||
<span className="flex items-center gap-1">
|
<div className="space-y-2">
|
||||||
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">←/→</kbd>
|
<div className="font-medium text-gray-900 dark:text-white">
|
||||||
上一帧/下一帧
|
{window.title}
|
||||||
</span>
|
</div>
|
||||||
<span className="flex items-center gap-1">
|
<div className="text-sm text-gray-500 dark:text-gray-400 break-all">
|
||||||
<kbd className="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-600 rounded text-xs">S</kbd>
|
{window.path}
|
||||||
切换星标
|
</div>
|
||||||
</span>
|
<div className="text-sm flex items-center text-gray-600 dark:text-gray-400">
|
||||||
</div>
|
内存占用: {formatMemory(window.memory)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 窗口信息 */}
|
|
||||||
<div className="w-full">
|
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-3">活动窗口</h3>
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 rounded-md p-4">
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
||||||
{selectedRecord.windows.map((window, index) => (
|
|
||||||
<div key={index} className="p-4 bg-white dark:bg-gray-800 rounded-md shadow-sm">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="font-medium text-gray-900 dark:text-white">
|
|
||||||
{window.title}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400 break-all">
|
|
||||||
{window.path}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm flex items-center text-gray-600 dark:text-gray-400">
|
|
||||||
内存占用: {formatMemory(window.memory)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user