mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-03-15 02:27:29 +08:00
feat: add right sidebar collapse
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
import { getStorage, IStorage } from '@/lib/db';
|
import { getStorage } from '@/lib/db';
|
||||||
|
import { IStorage } from '@/lib/types';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
import { getStorage, IStorage } from '@/lib/db';
|
import { getStorage } from '@/lib/db';
|
||||||
|
import { IStorage } from '@/lib/types';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { db, Favorite } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
|
import { Favorite } from '@/lib/types';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { PlayRecord } from '@/lib/db';
|
import { PlayRecord } from '@/lib/types';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ function PlayPageClient() {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 折叠状态(仅在 lg 及以上屏幕有效)
|
||||||
|
const [isEpisodeSelectorCollapsed, setIsEpisodeSelectorCollapsed] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
// 播放进度保存相关
|
// 播放进度保存相关
|
||||||
const saveIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const saveIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const lastSaveTimeRef = useRef<number>(0);
|
const lastSaveTimeRef = useRef<number>(0);
|
||||||
@@ -1207,7 +1211,7 @@ function PlayPageClient() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout activePath='/play'>
|
<PageLayout activePath='/play'>
|
||||||
<div className='flex flex-col gap-6 py-4 px-5 lg:px-10 xl:px-20'>
|
<div className='flex flex-col gap-3 py-4 px-5 lg:px-10 xl:px-20'>
|
||||||
{/* 第一行:影片标题 */}
|
{/* 第一行:影片标题 */}
|
||||||
<div className='py-1'>
|
<div className='py-1'>
|
||||||
<h1 className='text-xl font-semibold text-gray-900 dark:text-gray-100'>
|
<h1 className='text-xl font-semibold text-gray-900 dark:text-gray-100'>
|
||||||
@@ -1220,30 +1224,89 @@ function PlayPageClient() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
{/* 第二行:播放器和选集 */}
|
{/* 第二行:播放器和选集 */}
|
||||||
<div className='grid grid-cols-1 md:grid-cols-4 gap-4 lg:h-[500px] xl:h-[650px]'>
|
<div className='space-y-2'>
|
||||||
{/* 播放器 */}
|
{/* 折叠控制 - 仅在 lg 及以上屏幕显示 */}
|
||||||
<div className='md:col-span-3 h-full'>
|
<div className='hidden lg:flex justify-end'>
|
||||||
<div
|
<button
|
||||||
ref={artRef}
|
onClick={() =>
|
||||||
className='bg-black w-full h-[300px] lg:h-full rounded-xl overflow-hidden border border-white/0 dark:border-white/30'
|
setIsEpisodeSelectorCollapsed(!isEpisodeSelectorCollapsed)
|
||||||
></div>
|
}
|
||||||
|
className='group relative flex items-center space-x-1.5 px-3 py-1.5 rounded-full bg-white/80 hover:bg-white dark:bg-gray-800/80 dark:hover:bg-gray-800 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50 shadow-sm hover:shadow-md transition-all duration-200'
|
||||||
|
title={
|
||||||
|
isEpisodeSelectorCollapsed ? '显示选集面板' : '隐藏选集面板'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className={`w-3.5 h-3.5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${
|
||||||
|
isEpisodeSelectorCollapsed ? 'rotate-180' : 'rotate-0'
|
||||||
|
}`}
|
||||||
|
fill='none'
|
||||||
|
stroke='currentColor'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
strokeWidth='2'
|
||||||
|
d='M9 5l7 7-7 7'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className='text-xs font-medium text-gray-600 dark:text-gray-300'>
|
||||||
|
{isEpisodeSelectorCollapsed ? '显示' : '隐藏'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* 精致的状态指示点 */}
|
||||||
|
<div
|
||||||
|
className={`absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full transition-all duration-200 ${
|
||||||
|
isEpisodeSelectorCollapsed
|
||||||
|
? 'bg-orange-400 animate-pulse'
|
||||||
|
: 'bg-green-400'
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 选集和换源 */}
|
<div
|
||||||
<div className='md:col-span-1 h-[300px] lg:h-full md:overflow-hidden'>
|
className={`grid gap-4 lg:h-[500px] xl:h-[650px] transition-all duration-300 ease-in-out ${
|
||||||
<EpisodeSelector
|
isEpisodeSelectorCollapsed
|
||||||
totalEpisodes={totalEpisodes}
|
? 'grid-cols-1'
|
||||||
value={currentEpisodeIndex + 1}
|
: 'grid-cols-1 md:grid-cols-4'
|
||||||
onChange={handleEpisodeChange}
|
}`}
|
||||||
onSourceChange={handleSourceChange}
|
>
|
||||||
currentSource={currentSource}
|
{/* 播放器 */}
|
||||||
currentId={currentId}
|
<div
|
||||||
videoTitle={videoTitle}
|
className={`h-full transition-all duration-300 ease-in-out ${
|
||||||
availableSources={availableSources}
|
isEpisodeSelectorCollapsed ? 'col-span-1' : 'md:col-span-3'
|
||||||
onSearchSources={handleSearchSources}
|
}`}
|
||||||
sourceSearchLoading={sourceSearchLoading}
|
>
|
||||||
sourceSearchError={sourceSearchError}
|
<div
|
||||||
/>
|
ref={artRef}
|
||||||
|
className='bg-black w-full h-[300px] lg:h-full rounded-xl overflow-hidden border border-white/0 dark:border-white/30 shadow-lg'
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 选集和换源 - 在移动端始终显示,在 lg 及以上可折叠 */}
|
||||||
|
<div
|
||||||
|
className={`h-[300px] lg:h-full md:overflow-hidden transition-all duration-300 ease-in-out ${
|
||||||
|
isEpisodeSelectorCollapsed
|
||||||
|
? 'md:col-span-1 lg:hidden lg:opacity-0 lg:scale-95'
|
||||||
|
: 'md:col-span-1 lg:opacity-100 lg:scale-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<EpisodeSelector
|
||||||
|
totalEpisodes={totalEpisodes}
|
||||||
|
value={currentEpisodeIndex + 1}
|
||||||
|
onChange={handleEpisodeChange}
|
||||||
|
onSourceChange={handleSourceChange}
|
||||||
|
currentSource={currentSource}
|
||||||
|
currentId={currentId}
|
||||||
|
videoTitle={videoTitle}
|
||||||
|
availableSources={availableSources}
|
||||||
|
onSearchSources={handleSearchSources}
|
||||||
|
sourceSearchLoading={sourceSearchLoading}
|
||||||
|
sourceSearchError={sourceSearchError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
|
import { AdminConfig } from './admin.types';
|
||||||
import { RedisStorage } from './redis.db';
|
import { RedisStorage } from './redis.db';
|
||||||
import { AdminConfig, Favorite, IStorage, PlayRecord } from './types';
|
import { Favorite, IStorage, PlayRecord } from './types';
|
||||||
|
|
||||||
// 重新导出类型,保持向后兼容
|
|
||||||
export type { AdminConfig, Favorite, IStorage, PlayRecord };
|
|
||||||
|
|
||||||
// storage type 常量: 'localstorage' | 'database',默认 'localstorage'
|
// storage type 常量: 'localstorage' | 'database',默认 'localstorage'
|
||||||
const STORAGE_TYPE =
|
const STORAGE_TYPE =
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { createClient, RedisClientType } from 'redis';
|
import { createClient, RedisClientType } from 'redis';
|
||||||
|
|
||||||
import { AdminConfig, Favorite, IStorage, PlayRecord } from './types';
|
import { AdminConfig } from './admin.types';
|
||||||
|
import { Favorite, IStorage, PlayRecord } from './types';
|
||||||
|
|
||||||
// 搜索历史最大条数
|
// 搜索历史最大条数
|
||||||
const SEARCH_HISTORY_LIMIT = 20;
|
const SEARCH_HISTORY_LIMIT = 20;
|
||||||
|
|||||||
@@ -109,6 +109,3 @@ export interface DoubanResult {
|
|||||||
message: string;
|
message: string;
|
||||||
list: DoubanItem[];
|
list: DoubanItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出AdminConfig类型
|
|
||||||
export { AdminConfig };
|
|
||||||
|
|||||||
Reference in New Issue
Block a user