feat: start of git integration
This commit is contained in:
@@ -49,6 +49,12 @@ const ImportIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const GitIcon = () => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M22 11.73L12.27 2a1 1 0 0 0-1.41 0L8.84 4.02l2.56 2.56a1.2 1.2 0 0 1 1.52 1.53l2.47 2.47a1.2 1.2 0 1 1-.72.67l-2.3-2.3v6.06a1.2 1.2 0 1 1-.85 0V8.9a1.2 1.2 0 0 1-.66-1.59L8.35 4.8 2 11.16a1 1 0 0 0 0 1.41L11.73 22a1 1 0 0 0 1.41 0L22 13.14a1 1 0 0 0 0-1.41z"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ActivityBar: React.FC = () => {
|
||||
const { activeView, setActiveView, sidebarVisible, toggleSidebar, openTab, tabs, activeTabId } = useAppStore();
|
||||
|
||||
@@ -66,9 +72,10 @@ export const ActivityBar: React.FC = () => {
|
||||
|
||||
// Check if import sidebar is active
|
||||
const isImportActive = activeView === 'import' && sidebarVisible;
|
||||
const isGitActive = activeView === 'git' && sidebarVisible;
|
||||
|
||||
// Handle view click - toggle sidebar if clicking on active view, otherwise switch view
|
||||
const handleViewClick = (view: 'posts' | 'pages' | 'media' | 'chat') => {
|
||||
const handleViewClick = (view: 'posts' | 'pages' | 'media' | 'chat' | 'git') => {
|
||||
if (activeView === view && sidebarVisible) {
|
||||
// Clicking on active view toggles sidebar off
|
||||
toggleSidebar();
|
||||
@@ -162,6 +169,13 @@ export const ActivityBar: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="activity-bar-bottom">
|
||||
<button
|
||||
className={`activity-bar-item ${isGitActive ? 'active' : ''}`}
|
||||
onClick={() => handleViewClick('git')}
|
||||
title="Source Control (click again to toggle sidebar)"
|
||||
>
|
||||
<GitIcon />
|
||||
</button>
|
||||
<button
|
||||
className={`activity-bar-item ${isSettingsActive ? 'active' : ''}`}
|
||||
onClick={handleSettingsClick}
|
||||
|
||||
53
src/renderer/components/GitSidebar/GitSidebar.css
Normal file
53
src/renderer/components/GitSidebar/GitSidebar.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.git-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.git-sidebar-header {
|
||||
padding: 10px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--vscode-sideBar-foreground);
|
||||
}
|
||||
|
||||
.git-sidebar-empty {
|
||||
padding: 16px 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.git-sidebar-empty p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.git-sidebar-error {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.git-sidebar-input {
|
||||
width: 100%;
|
||||
margin: 0 0 10px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.git-sidebar-button {
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.git-sidebar-button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
133
src/renderer/components/GitSidebar/GitSidebar.tsx
Normal file
133
src/renderer/components/GitSidebar/GitSidebar.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useAppStore } from '../../store';
|
||||
import './GitSidebar.css';
|
||||
|
||||
export const GitSidebar: React.FC = () => {
|
||||
const { activeProject } = useAppStore();
|
||||
const [projectPath, setProjectPath] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [initializing, setInitializing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isRepo, setIsRepo] = useState(false);
|
||||
const [currentBranch, setCurrentBranch] = useState<string | null>(null);
|
||||
const remoteUrlInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const resolveProjectPath = useCallback(async (): Promise<string | null> => {
|
||||
if (!activeProject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (activeProject.dataPath) {
|
||||
return activeProject.dataPath;
|
||||
}
|
||||
|
||||
return window.electronAPI.app.getDefaultProjectPath(activeProject.id);
|
||||
}, [activeProject]);
|
||||
|
||||
const loadRepoState = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const availability = await window.electronAPI.git.checkAvailability();
|
||||
if (!availability.gitFound) {
|
||||
setError('Git executable not found. Please install Git and restart the app.');
|
||||
setIsRepo(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedProjectPath = await resolveProjectPath();
|
||||
setProjectPath(resolvedProjectPath);
|
||||
|
||||
if (!resolvedProjectPath) {
|
||||
setError('No active project selected.');
|
||||
setIsRepo(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const repoState = await window.electronAPI.git.getRepoState(resolvedProjectPath);
|
||||
setIsRepo(repoState.isRepo);
|
||||
setCurrentBranch(repoState.currentBranch || null);
|
||||
} catch {
|
||||
setError('Unable to load repository status.');
|
||||
setIsRepo(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [resolveProjectPath]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadRepoState();
|
||||
}, [loadRepoState]);
|
||||
|
||||
const handleInitialize = async () => {
|
||||
if (!projectPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInitializing(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const normalizedRemoteUrl = remoteUrlInputRef.current?.value.trim() || '';
|
||||
const result = normalizedRemoteUrl
|
||||
? await window.electronAPI.git.init(projectPath, normalizedRemoteUrl)
|
||||
: await window.electronAPI.git.init(projectPath);
|
||||
if (!result.success) {
|
||||
setError(result.error || 'Failed to initialize git repository.');
|
||||
return;
|
||||
}
|
||||
|
||||
await loadRepoState();
|
||||
} catch {
|
||||
setError('Failed to initialize git repository.');
|
||||
} finally {
|
||||
setInitializing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="git-sidebar">
|
||||
<div className="git-sidebar-header">SOURCE CONTROL</div>
|
||||
<div className="git-sidebar-empty">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isRepo) {
|
||||
return (
|
||||
<div className="git-sidebar">
|
||||
<div className="git-sidebar-header">SOURCE CONTROL</div>
|
||||
<div className="git-sidebar-empty">
|
||||
<p>Git repository ready</p>
|
||||
{currentBranch && <p>Branch: {currentBranch}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="git-sidebar">
|
||||
<div className="git-sidebar-header">SOURCE CONTROL</div>
|
||||
<div className="git-sidebar-empty">
|
||||
<p>This project is not a git repository.</p>
|
||||
<input
|
||||
ref={remoteUrlInputRef}
|
||||
className="git-sidebar-input"
|
||||
type="text"
|
||||
placeholder="Optional remote repository URL"
|
||||
disabled={initializing}
|
||||
/>
|
||||
{error && <p className="git-sidebar-error">{error}</p>}
|
||||
<button
|
||||
className="git-sidebar-button"
|
||||
onClick={handleInitialize}
|
||||
disabled={initializing || !projectPath}
|
||||
>
|
||||
{initializing ? 'Initializing...' : 'Initialize Git'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { useAppStore, PostData, MediaData } from '../../store';
|
||||
import { showToast } from '../Toast';
|
||||
import { getContrastColor, groupPostsByStatus } from '../../utils';
|
||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
||||
import './Sidebar.css';
|
||||
|
||||
/** Get display name for media: title (truncated to 60 chars) or fallback to filename */
|
||||
@@ -1640,6 +1641,7 @@ export const Sidebar: React.FC = () => {
|
||||
{activeView === 'tags' && <TagsNav />}
|
||||
{activeView === 'chat' && <ChatList />}
|
||||
{activeView === 'import' && <ImportList />}
|
||||
{activeView === 'git' && <GitSidebar />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user