polishing
This commit is contained in:
@ -617,105 +617,114 @@ export default function App() {
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowFileMenu(false)} />
|
||||
<div className="absolute left-0 top-full mt-1 z-50 w-44 rounded-lg border border-editor-border bg-editor-surface shadow-xl py-1">
|
||||
<DropdownItem icon={<FilePlus2 className="w-4 h-4" />} label="New Project" onClick={() => { setShowFileMenu(false); handleNewProject(); }} />
|
||||
<DropdownItem icon={<FolderOpen className="w-4 h-4" />} label="Open File" onClick={() => { setShowFileMenu(false); handleOpenFile(); }} />
|
||||
<DropdownItem icon={<FileInput className="w-4 h-4" />} label="Load Project" onClick={() => { setShowFileMenu(false); handleLoadProject(); }} />
|
||||
<DropdownItem icon={<FilePlus2 className="w-4 h-4" />} label="New Project" title="Start a new empty project" onClick={() => { setShowFileMenu(false); handleNewProject(); }} />
|
||||
<DropdownItem icon={<FolderOpen className="w-4 h-4" />} label="Open File" title="Open a video or audio file for transcription" onClick={() => { setShowFileMenu(false); handleOpenFile(); }} />
|
||||
<DropdownItem icon={<FileInput className="w-4 h-4" />} label="Load Project" title="Open a saved .aive project file" onClick={() => { setShowFileMenu(false); handleLoadProject(); }} />
|
||||
<div className="border-t border-editor-border my-1" />
|
||||
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save" onClick={() => { setShowFileMenu(false); handleSaveProject(); }} disabled={words.length === 0} />
|
||||
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save As" onClick={() => { setShowFileMenu(false); handleSaveProjectAs(); }} disabled={words.length === 0} />
|
||||
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save" title="Save current project" onClick={() => { setShowFileMenu(false); handleSaveProject(); }} disabled={words.length === 0} />
|
||||
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save As" title="Save a copy of the current project" onClick={() => { setShowFileMenu(false); handleSaveProjectAs(); }} disabled={words.length === 0} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ToolbarButton
|
||||
icon={<Scissors className="w-4 h-4" />}
|
||||
label="Cut"
|
||||
onClick={handleCut}
|
||||
active={cutMode}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<VolumeX className="w-4 h-4" />}
|
||||
label="Mute"
|
||||
onClick={handleMute}
|
||||
active={muteMode}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
<div className="flex items-center gap-1">
|
||||
<ToolbarButton
|
||||
icon={<SlidersHorizontal className="w-4 h-4" />}
|
||||
label="Gain Zone"
|
||||
onClick={handleGain}
|
||||
active={gainMode}
|
||||
icon={<Scissors className="w-4 h-4" />}
|
||||
label="Cut"
|
||||
onClick={handleCut}
|
||||
active={cutMode}
|
||||
disabled={!canEdit}
|
||||
title="Cut selected word range or mark in/out area — removes the segment from output"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={-24}
|
||||
max={24}
|
||||
step={0.5}
|
||||
value={gainModeDb}
|
||||
onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
|
||||
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
|
||||
title="Gain dB for new gain zones"
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<ToolbarButton
|
||||
icon={<Gauge className="w-4 h-4" />}
|
||||
label="Speed Zone"
|
||||
onClick={handleSpeed}
|
||||
active={speedMode}
|
||||
icon={<VolumeX className="w-4 h-4" />}
|
||||
label="Mute"
|
||||
onClick={handleMute}
|
||||
active={muteMode}
|
||||
disabled={!canEdit}
|
||||
title="Mute selected word range or mark in/out area — silences audio, keeps video"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={0.25}
|
||||
max={4}
|
||||
step={0.05}
|
||||
value={speedModeValue}
|
||||
onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))}
|
||||
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
|
||||
title="Playback rate for new speed zones"
|
||||
disabled={!canEdit}
|
||||
<div className="flex items-center gap-1">
|
||||
<ToolbarButton
|
||||
icon={<SlidersHorizontal className="w-4 h-4" />}
|
||||
label="Gain Zone"
|
||||
onClick={handleGain}
|
||||
active={gainMode}
|
||||
disabled={!canEdit}
|
||||
title="Add gain zone from selection or mark in/out — adjust volume up or down"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={-24}
|
||||
max={24}
|
||||
step={0.5}
|
||||
value={gainModeDb}
|
||||
onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
|
||||
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
|
||||
data-tooltip="Volume adjustment in decibels for new gain zones — positive boosts, negative reduces"
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<ToolbarButton
|
||||
icon={<Gauge className="w-4 h-4" />}
|
||||
label="Speed Zone"
|
||||
onClick={handleSpeed}
|
||||
active={speedMode}
|
||||
disabled={!canEdit}
|
||||
title="Add speed zone from selection or mark in/out — change playback speed"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={0.25}
|
||||
max={4}
|
||||
step={0.05}
|
||||
value={speedModeValue}
|
||||
onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))}
|
||||
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
|
||||
data-tooltip="Playback speed multiplier for new speed zones — 1x is normal, 2x is double speed"
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</div>
|
||||
<ToolbarButton
|
||||
icon={<Grid3x3 className="w-4 h-4" />}
|
||||
label="Zones"
|
||||
active={activePanel === 'zones'}
|
||||
onClick={() => togglePanel('zones')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
title="Open zone editor panel — view and manage all cut, mute, gain, and speed zones"
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<span className="text-[10px] font-semibold">PA</span>}
|
||||
label="Pause Trim"
|
||||
active={activePanel === 'silence'}
|
||||
onClick={() => togglePanel('silence')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
title="Detect and remove silent pauses — batch-removes silence above a configurable threshold"
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<MapPin className="w-4 h-4" />}
|
||||
label="Markers"
|
||||
active={activePanel === 'markers'}
|
||||
onClick={() => togglePanel('markers')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
title="Add and manage timeline markers — chapter points, key moments, YouTube timestamps"
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<Music className="w-4 h-4" />}
|
||||
label="Music"
|
||||
active={activePanel === 'music'}
|
||||
onClick={() => togglePanel('music')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
title="Add background music track with auto-ducking — music lowers when someone speaks"
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<ListVideo className="w-4 h-4" />}
|
||||
label="Append"
|
||||
active={activePanel === 'append'}
|
||||
onClick={() => togglePanel('append')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
title="Append additional video clips — concatenate multiple files during export"
|
||||
/>
|
||||
</div>
|
||||
<ToolbarButton
|
||||
icon={<Grid3x3 className="w-4 h-4" />}
|
||||
label="Zones"
|
||||
active={activePanel === 'zones'}
|
||||
onClick={() => togglePanel('zones')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<span className="text-[10px] font-semibold">PA</span>}
|
||||
label="Pause Trim"
|
||||
active={activePanel === 'silence'}
|
||||
onClick={() => togglePanel('silence')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<MapPin className="w-4 h-4" />}
|
||||
label="Markers"
|
||||
active={activePanel === 'markers'}
|
||||
onClick={() => togglePanel('markers')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<Music className="w-4 h-4" />}
|
||||
label="Music"
|
||||
active={activePanel === 'music'}
|
||||
onClick={() => togglePanel('music')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<ListVideo className="w-4 h-4" />}
|
||||
label="Append"
|
||||
active={activePanel === 'append'}
|
||||
onClick={() => togglePanel('append')}
|
||||
disabled={!videoPath || !canEdit}
|
||||
/>
|
||||
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-editor-surface border border-editor-border">
|
||||
<select
|
||||
value={whisperModel}
|
||||
@ -745,7 +754,7 @@ export default function App() {
|
||||
<button
|
||||
onClick={handleReprocessProject}
|
||||
disabled={isTranscribing || !videoPath || !canEdit}
|
||||
title="Reprocess transcript with selected model"
|
||||
data-tooltip="Re-run transcription with the selected Whisper model — replaces current transcript"
|
||||
className="flex items-center gap-1 px-2 py-1 rounded text-xs text-editor-text hover:bg-editor-bg disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw className={`w-3 h-3 ${isTranscribing ? 'animate-spin' : ''}`} />
|
||||
@ -758,6 +767,7 @@ export default function App() {
|
||||
active={activePanel === 'ai'}
|
||||
onClick={() => togglePanel('ai')}
|
||||
disabled={words.length === 0 || !canEdit}
|
||||
title="AI filler detection, clip suggestions, and transcript analysis"
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={<Download className="w-4 h-4" />}
|
||||
@ -964,27 +974,30 @@ function ToolbarButton({
|
||||
active,
|
||||
onClick,
|
||||
disabled,
|
||||
title,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
active?: boolean;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
title={label}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors ${
|
||||
active
|
||||
? 'bg-editor-accent text-white'
|
||||
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-surface'
|
||||
} ${disabled ? 'opacity-40 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
<span data-tooltip={title || label}>
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors ${
|
||||
active
|
||||
? 'bg-editor-accent text-white'
|
||||
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-surface'
|
||||
} ${disabled ? 'opacity-40 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -993,24 +1006,28 @@ function DropdownItem({
|
||||
label,
|
||||
onClick,
|
||||
disabled,
|
||||
title,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors ${
|
||||
disabled
|
||||
? 'opacity-40 cursor-not-allowed'
|
||||
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-bg'
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
<span data-tooltip={title || label}>
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors ${
|
||||
disabled
|
||||
? 'opacity-40 cursor-not-allowed'
|
||||
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-bg'
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user