added licensing stuff and free trial timer

This commit is contained in:
2026-05-06 01:35:42 -06:00
parent 810957747b
commit 835719a907
13 changed files with 1121 additions and 53 deletions

View File

@ -12,7 +12,9 @@ import SilenceTrimmerPanel from './components/SilenceTrimmerPanel';
import ZoneEditor from './components/ZoneEditor';
import BackgroundMusicPanel from './components/BackgroundMusicPanel';
import AppendClipPanel from './components/AppendClipPanel';
import LicenseDialog from './components/LicenseDialog';
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
import { useLicenseStore } from './store/licenseStore';
import {
Film,
FolderOpen,
@ -132,6 +134,8 @@ export default function App() {
const [showUnsavedPrompt, setShowUnsavedPrompt] = useState(false);
const [pendingProceedAction, setPendingProceedAction] = useState<(() => Promise<void>) | null>(null);
const [lastSavedSignature, setLastSavedSignature] = useState<string | null>(null);
const [showFileMenu, setShowFileMenu] = useState(false);
const canEdit = useLicenseStore((s) => s.canEdit);
const projectSignature = useMemo(() => {
if (!videoPath) return null;
@ -196,7 +200,11 @@ export default function App() {
useKeyboardShortcuts();
// Handle Escape key to exit timeline zone modes
useEffect(() => {
useLicenseStore.getState().checkStatus();
}, []);
// Handle Escape key to exit timeline zone modes and close menus
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
@ -204,9 +212,9 @@ export default function App() {
setMuteMode(false);
setGainMode(false);
setSpeedMode(false);
setShowFileMenu(false);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
@ -287,7 +295,9 @@ export default function App() {
setProjectFilePath(null);
setProjectName(null);
loadVideo(path);
await transcribeVideo(path);
if (canEdit) {
await transcribeVideo(path);
}
}
});
};
@ -562,44 +572,40 @@ export default function App() {
{/* Top bar */}
<header className="h-12 flex items-center px-4 border-b border-editor-border shrink-0">
<div className="flex items-center gap-0.5">
<ToolbarButton
icon={<FilePlus2 className="w-4 h-4" />}
label="New"
onClick={handleNewProject}
/>
<ToolbarButton
icon={<FolderOpen className="w-4 h-4" />}
label="Open"
onClick={handleOpenFile}
/>
<ToolbarButton
icon={<Save className="w-4 h-4" />}
label="Save"
onClick={handleSaveProject}
disabled={words.length === 0}
/>
<ToolbarButton
icon={<Save className="w-4 h-4" />}
label="Save As"
onClick={handleSaveProjectAs}
disabled={words.length === 0}
/>
<ToolbarButton
icon={<FileInput className="w-4 h-4" />}
label="Load"
onClick={handleLoadProject}
/>
<div className="relative">
<ToolbarButton
icon={<FolderOpen className="w-4 h-4" />}
label="File"
onClick={() => setShowFileMenu((p) => !p)}
active={showFileMenu}
/>
{showFileMenu && (
<>
<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(); }} />
<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} />
</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
@ -607,6 +613,7 @@ export default function App() {
label="Gain Zone"
onClick={handleGain}
active={gainMode}
disabled={!canEdit}
/>
<input
type="number"
@ -617,6 +624,7 @@ export default function App() {
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">
@ -625,6 +633,7 @@ export default function App() {
label="Speed Zone"
onClick={handleSpeed}
active={speedMode}
disabled={!canEdit}
/>
<input
type="number"
@ -635,6 +644,7 @@ export default function App() {
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>
<ToolbarButton
@ -642,35 +652,35 @@ export default function App() {
label="Zones"
active={activePanel === 'zones'}
onClick={() => togglePanel('zones')}
disabled={!videoPath}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<span className="text-[10px] font-semibold">PA</span>}
label="Pause Trim"
active={activePanel === 'silence'}
onClick={() => togglePanel('silence')}
disabled={!videoPath}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<MapPin className="w-4 h-4" />}
label="Markers"
active={activePanel === 'markers'}
onClick={() => togglePanel('markers')}
disabled={!videoPath}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<Music className="w-4 h-4" />}
label="Music"
active={activePanel === 'music'}
onClick={() => togglePanel('music')}
disabled={!videoPath}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<ListVideo className="w-4 h-4" />}
label="Append"
active={activePanel === 'append'}
onClick={() => togglePanel('append')}
disabled={!videoPath}
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
@ -700,7 +710,7 @@ export default function App() {
</select>
<button
onClick={handleReprocessProject}
disabled={isTranscribing || !videoPath}
disabled={isTranscribing || !videoPath || !canEdit}
title="Reprocess transcript with selected model"
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"
>
@ -713,7 +723,7 @@ export default function App() {
label="AI"
active={activePanel === 'ai'}
onClick={() => togglePanel('ai')}
disabled={words.length === 0}
disabled={words.length === 0 || !canEdit}
/>
<ToolbarButton
icon={<Download className="w-4 h-4" />}
@ -841,6 +851,8 @@ export default function App() {
</div>
{import.meta.env.DEV && <DevPanel />}
<LicenseDialog />
{showReprocessConfirm && (
<div
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/60 px-4"
@ -941,3 +953,30 @@ function ToolbarButton({
</button>
);
}
function DropdownItem({
icon,
label,
onClick,
disabled,
}: {
icon: React.ReactNode;
label: string;
onClick: () => void;
disabled?: boolean;
}) {
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>
);
}