import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useAppStore } from '../../store'; import './Panel.css'; type PanelTab = 'tasks' | 'output' | 'git-log'; function getPostRelativePath(createdAt: string, slug: string): string | null { const createdDate = new Date(createdAt); if (Number.isNaN(createdDate.getTime())) { return null; } const year = String(createdDate.getFullYear()); const month = String(createdDate.getMonth() + 1).padStart(2, '0'); return `posts/${year}/${month}/${slug}.md`; } function normalizePath(value: string): string { return value.replace(/\\/g, '/').replace(/\/+$/, ''); } function toRelativePath(absolutePath: string, projectPath: string): string { const normalizedAbsolute = normalizePath(absolutePath); const normalizedProject = normalizePath(projectPath); if (normalizedAbsolute.toLowerCase() === normalizedProject.toLowerCase()) { return ''; } const prefix = `${normalizedProject}/`; if (normalizedAbsolute.toLowerCase().startsWith(prefix.toLowerCase())) { return normalizedAbsolute.slice(prefix.length); } return normalizedAbsolute; } export const Panel: React.FC = () => { const { panelVisible, tasks, tabs, activeTabId, posts, media, activeProject } = useAppStore(); const [activePanelTab, setActivePanelTab] = useState('tasks'); const [gitLogLoading, setGitLogLoading] = useState(false); const [gitLogError, setGitLogError] = useState(null); const [gitLogTargetLabel, setGitLogTargetLabel] = useState(null); const [gitLogEntries, setGitLogEntries] = useState>([]); const requestIdRef = useRef(0); const recentTasks = tasks.slice(-10).reverse(); const activeEditorTab = useMemo(() => tabs.find((tab) => tab.id === activeTabId) ?? null, [tabs, activeTabId]); const canActivateGitLog = activeEditorTab?.type === 'post' || activeEditorTab?.type === 'media'; useEffect(() => { if (!canActivateGitLog && activePanelTab === 'git-log') { setActivePanelTab('tasks'); } }, [canActivateGitLog, activePanelTab]); useEffect(() => { const projectPath = activeProject?.dataPath; if (!projectPath || !activeEditorTab || (activeEditorTab.type !== 'post' && activeEditorTab.type !== 'media')) { setGitLogEntries([]); setGitLogTargetLabel(null); setGitLogError(null); setGitLogLoading(false); return; } const currentRequestId = ++requestIdRef.current; const loadFileHistory = async () => { setGitLogLoading(true); setGitLogError(null); try { let targetLabel = ''; let relativeFilePath = ''; if (activeEditorTab.type === 'post') { const post = posts.find((item) => item.id === activeEditorTab.id) || await window.electronAPI?.posts.get(activeEditorTab.id); if (!post) { setGitLogEntries([]); setGitLogTargetLabel(null); setGitLogLoading(false); return; } targetLabel = post.title || post.slug; relativeFilePath = getPostRelativePath(post.createdAt, post.slug) || ''; } else { const mediaItem = media.find((item) => item.id === activeEditorTab.id) || await window.electronAPI?.media.get(activeEditorTab.id); if (!mediaItem) { setGitLogEntries([]); setGitLogTargetLabel(null); setGitLogLoading(false); return; } targetLabel = mediaItem.title || mediaItem.originalName; const absoluteMediaPath = await window.electronAPI?.media.getFilePath(activeEditorTab.id); if (!absoluteMediaPath) { setGitLogEntries([]); setGitLogTargetLabel(targetLabel); setGitLogLoading(false); return; } relativeFilePath = toRelativePath(absoluteMediaPath, projectPath); } if (!relativeFilePath) { setGitLogEntries([]); setGitLogTargetLabel(targetLabel || null); setGitLogLoading(false); return; } const entries = await window.electronAPI?.git.getFileHistory(projectPath, relativeFilePath, 50); if (requestIdRef.current !== currentRequestId) { return; } setGitLogEntries(entries || []); setGitLogTargetLabel(targetLabel || relativeFilePath); } catch (error) { if (requestIdRef.current !== currentRequestId) { return; } setGitLogError(error instanceof Error ? error.message : 'Failed to load git log.'); setGitLogEntries([]); } finally { if (requestIdRef.current === currentRequestId) { setGitLogLoading(false); } } }; void loadFileHistory(); }, [activeEditorTab, activeProject?.dataPath, posts, media]); if (!panelVisible) { return null; } return (
{activePanelTab === 'tasks' && ( recentTasks.length === 0 ? (
No recent tasks
) : (
{recentTasks.map(task => (
{task.status === 'running' && } {task.status === 'completed' && } {task.status === 'failed' && } {task.status === 'pending' && }
{task.message}
{task.status === 'running' && (
)}
{task.status === 'running' && ( )}
))}
) )} {activePanelTab === 'output' && (
No output
)} {activePanelTab === 'git-log' && ( !canActivateGitLog ? (
Open a post or media editor to view git log
) : gitLogLoading ? (
Loading git log...
) : gitLogError ? (
{gitLogError}
) : gitLogEntries.length === 0 ? (
No commits found for this item
) : (
{gitLogTargetLabel}
{gitLogEntries.map((entry) => (
{entry.subject}
{entry.shortHash} {entry.author} {new Date(entry.date).toLocaleString()}
))}
) )}
); };