forked from 77media/video-flow
PortraitAnimeSelector
This commit is contained in:
parent
1c2421e2b6
commit
5c33805284
@ -3,14 +3,13 @@
|
||||
import {
|
||||
GlobalOutlined,
|
||||
ClockCircleOutlined,
|
||||
DownOutlined,
|
||||
MobileOutlined,
|
||||
DesktopOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { WandSparkles, RectangleHorizontal, RectangleVertical } from 'lucide-react';
|
||||
import { Dropdown, Menu, Tooltip } from 'antd';
|
||||
import { LanguageOptions, VideoDurationOptions } from './config-options';
|
||||
import type { ConfigOptions, LanguageValue, VideoDurationValue } from './config-options';
|
||||
import { PortraitAnimeSelector } from './PortraitAnimeSelector';
|
||||
|
||||
interface ConfigPanelProps {
|
||||
/** Current configuration options */
|
||||
@ -115,7 +114,7 @@ export const ConfigPanel = ({
|
||||
</Dropdown>
|
||||
|
||||
{/* Aspect ratio toggles */}
|
||||
<div data-alt="aspect-ratio-controls" className="h-8 px-2 py-1 flex items-center gap-1 rounded-full border border-white/20 hover:border-cyan-400/60">
|
||||
<div data-alt="aspect-ratio-controls" className="h-8 px-2 py-1 flex items-center rounded-full border border-white/20 hover:border-cyan-400/60">
|
||||
<button
|
||||
data-alt="portrait-button"
|
||||
className={`w-8 h-6 rounded-full transition-all duration-200 flex items-center justify-center ${
|
||||
@ -139,6 +138,12 @@ export const ConfigPanel = ({
|
||||
<RectangleHorizontal className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Portrait/Anime selector */}
|
||||
<PortraitAnimeSelector
|
||||
value={configOptions.pcode}
|
||||
onChange={(v) => onConfigChange('pcode', v)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { fetchSettingByCode } from '@/api/serversetting';
|
||||
import type { PortraitAnimeValue } from './config-options';
|
||||
|
||||
interface PortraitAnimeSelectorProps {
|
||||
/** Current value: 'portrait' or anime pcode */
|
||||
value: PortraitAnimeValue;
|
||||
/** Change handler */
|
||||
onChange: (value: PortraitAnimeValue) => void;
|
||||
/** Optional class name */
|
||||
className?: string;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Portrait/Anime selector with dropdown for Anime subtypes.
|
||||
* Styled to match VideoCreationForm design.
|
||||
* @param {PortraitAnimeValue} value - Current value: 'portrait' or anime pcode
|
||||
* @param {(value: PortraitAnimeValue) => void} onChange - Change handler
|
||||
* @param {string} [className] - Optional wrapper class
|
||||
* @param {boolean} [disabled] - Disable interaction when true
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function PortraitAnimeSelector({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
disabled = false,
|
||||
}: PortraitAnimeSelectorProps) {
|
||||
const [lastAnimeChoice, setLastAnimeChoice] = useState<string>('STANDARD_V1_734684_116483');
|
||||
const [animeOptions, setAnimeOptions] = useState<Array<{ name: string; pcode: string }>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value && value !== 'portrait') {
|
||||
setLastAnimeChoice(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
const list = await fetchSettingByCode<Array<{ name: string; pcode: string }>>('comic_config', []);
|
||||
if (!mounted) return;
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
setAnimeOptions(list);
|
||||
setLastAnimeChoice((prev) => (prev === 'STANDARD_V1_734684_116483' ? list[0].pcode : prev));
|
||||
} else {
|
||||
setAnimeOptions([
|
||||
{ name: 'Korean Comics Long', pcode: 'STANDARD_V1_734684_116483' },
|
||||
]);
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
const isPortrait = value === 'portrait';
|
||||
const currentAnime = useMemo(() => (value && value !== 'portrait' ? value : lastAnimeChoice), [value, lastAnimeChoice]);
|
||||
const pcodeToName = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
animeOptions.forEach((o) => { map[o.pcode] = o.name; });
|
||||
return map;
|
||||
}, [animeOptions]);
|
||||
|
||||
/** Anime dropdown menu */
|
||||
const animeMenu = (
|
||||
<Menu
|
||||
className="bg-[#1a1a1a] border border-white/10"
|
||||
onClick={({ key }) => onChange(key as PortraitAnimeValue)}
|
||||
items={(animeOptions.length > 0 ? animeOptions : []).map((opt) => ({
|
||||
key: opt.pcode,
|
||||
label: <span className="text-gray-300 text-sm">{opt.name}</span>,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-alt="portrait-anime-selector"
|
||||
className={`h-8 px-2 py-1 flex items-center rounded-full border border-white/20 hover:border-cyan-400/60 ${className || ''}`}
|
||||
>
|
||||
{/* Portrait button */}
|
||||
<button
|
||||
data-alt="portrait-button"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onChange('portrait')}
|
||||
className={`h-6 px-2 rounded-full transition-all duration-200 flex items-center text-sm ${
|
||||
disabled
|
||||
? 'opacity-40 cursor-not-allowed bg-transparent text-gray-400'
|
||||
: isPortrait
|
||||
? 'bg-white/10 text-cyan-400 shadow-sm'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
Portrait
|
||||
</button>
|
||||
|
||||
{/* Anime dropdown button */}
|
||||
<Dropdown overlay={animeMenu} trigger={['click']} disabled={disabled}>
|
||||
<button
|
||||
data-alt="anime-button"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={`h-6 px-2 rounded-full transition-all duration-200 flex items-center gap-1.5 text-sm ${
|
||||
disabled
|
||||
? 'opacity-40 cursor-not-allowed bg-transparent text-gray-400'
|
||||
: !isPortrait
|
||||
? 'bg-white/10 text-cyan-400 shadow-sm'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="max-w-[100px] truncate">
|
||||
{!isPortrait && pcodeToName[currentAnime]
|
||||
? pcodeToName[currentAnime]
|
||||
: 'Anime'}
|
||||
</span>
|
||||
<DownOutlined className="text-xs" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PortraitAnimeSelector;
|
||||
|
||||
@ -6,11 +6,12 @@ Video creation input form with configuration options.
|
||||
|
||||
```
|
||||
CreateInput/
|
||||
├── VideoCreationForm.tsx # Main form component
|
||||
├── ConfigPanel.tsx # Configuration panel with all options
|
||||
├── config-options.ts # Configuration options and types
|
||||
├── index.ts # Module exports
|
||||
└── README.md # This file
|
||||
├── VideoCreationForm.tsx # Main form component
|
||||
├── ConfigPanel.tsx # Configuration panel with all options
|
||||
├── PortraitAnimeSelector.tsx # Portrait/Anime style selector
|
||||
├── config-options.ts # Configuration options and types
|
||||
├── index.ts # Module exports
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Components
|
||||
@ -28,6 +29,7 @@ Configuration panel with unified circular button style:
|
||||
- Auto Script toggle (AI Story Copilot)
|
||||
- Duration selector (8s, 1min, 2min, auto)
|
||||
- Aspect ratio selector (landscape/portrait)
|
||||
- Portrait/Anime style selector
|
||||
|
||||
All buttons follow the same design pattern:
|
||||
- Circular buttons with `rounded-full`
|
||||
@ -36,6 +38,13 @@ All buttons follow the same design pattern:
|
||||
- Active state: `border-cyan-400 bg-cyan-400/10`
|
||||
- Cyan color theme for interactions
|
||||
|
||||
### PortraitAnimeSelector
|
||||
Selector for choosing between Portrait and Anime (comic) styles:
|
||||
- Portrait mode: realistic portrait style
|
||||
- Anime mode: comic/anime styles with multiple options (Korean Comics Long, etc.)
|
||||
- Fetches anime options from server dynamically
|
||||
- Styled to match other configuration buttons
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Defined in `config-options.ts`:
|
||||
@ -46,9 +55,18 @@ interface ConfigOptions {
|
||||
expansion_mode: boolean; // Default: false
|
||||
videoDuration: VideoDurationValue; // Default: 'unlimited'
|
||||
aspect_ratio: AspectRatioValue; // Default: 'VIDEO_ASPECT_RATIO_LANDSCAPE'
|
||||
pcode: PortraitAnimeValue; // Default: 'portrait'
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Options Details
|
||||
|
||||
- **language**: Video output language (14 options: English, Chinese, Japanese, Spanish, etc.)
|
||||
- **expansion_mode**: AI Story Copilot toggle (auto-generates extended script)
|
||||
- **videoDuration**: Video length (8s, 1min, 2min, auto)
|
||||
- **aspect_ratio**: Video orientation (landscape/portrait)
|
||||
- **pcode**: Visual style (portrait or anime/comic style codes)
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
|
||||
@ -74,7 +74,7 @@ export default function VideoCreationForm() {
|
||||
>
|
||||
{/* Photo Preview Section - Top */}
|
||||
{photos.length > 0 && (
|
||||
<div data-alt="photo-preview-wrapper" className="px-6 pt-6">
|
||||
<div data-alt="photo-preview-wrapper" className="p-4 pb-0">
|
||||
<PhotoPreviewSection
|
||||
photos={photos}
|
||||
onDelete={handleDeletePhoto}
|
||||
|
||||
@ -33,12 +33,16 @@ export type VideoDurationValue = typeof VideoDurationOptions[number]['value'];
|
||||
/** Aspect ratio type */
|
||||
export type AspectRatioValue = 'VIDEO_ASPECT_RATIO_LANDSCAPE' | 'VIDEO_ASPECT_RATIO_PORTRAIT';
|
||||
|
||||
/** Portrait/Anime type */
|
||||
export type PortraitAnimeValue = 'portrait' | string;
|
||||
|
||||
/** Configuration options interface */
|
||||
export interface ConfigOptions {
|
||||
language: LanguageValue;
|
||||
expansion_mode: boolean;
|
||||
videoDuration: VideoDurationValue;
|
||||
aspect_ratio: AspectRatioValue;
|
||||
pcode: PortraitAnimeValue;
|
||||
}
|
||||
|
||||
/** Default configuration */
|
||||
@ -47,5 +51,6 @@ export const defaultConfig: ConfigOptions = {
|
||||
expansion_mode: false,
|
||||
videoDuration: 'unlimited',
|
||||
aspect_ratio: 'VIDEO_ASPECT_RATIO_LANDSCAPE',
|
||||
pcode: 'portrait',
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export { default as VideoCreationForm } from './VideoCreationForm';
|
||||
export { ConfigPanel } from './ConfigPanel';
|
||||
export { PortraitAnimeSelector } from './PortraitAnimeSelector';
|
||||
export * from './config-options';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user