diff --git a/frontend/src/components/settings/CommitSection.jsx b/frontend/src/components/settings/CommitSection.jsx
index 100ef1c..7d9e12a 100644
--- a/frontend/src/components/settings/CommitSection.jsx
+++ b/frontend/src/components/settings/CommitSection.jsx
@@ -38,12 +38,6 @@ const CommitSection = ({
/>
>
)}
- {hasUnstagedChanges && !hasStagedChanges && (
-
- You have unstaged changes. Stage your
- changes before committing.
-
- )}
>
) : (
diff --git a/frontend/src/components/settings/DiffModal.jsx b/frontend/src/components/settings/DiffModal.jsx
deleted file mode 100644
index d58c800..0000000
--- a/frontend/src/components/settings/DiffModal.jsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-import Modal from "../ui/Modal";
-
-const DiffModal = ({
- isOpen,
- onClose,
- diffContent,
- type,
- name,
- commitMessage,
- title = "View Diff",
-}) => {
- const formatDiffContent = (content) => {
- if (!content) return [];
- return content.split("\n").map((line, index) => {
- let lineClass = "py-1 pl-4 border-l-2 ";
- if (line.startsWith("+")) {
- lineClass += "bg-green-900/30 text-green-400 border-green-500";
- } else if (line.startsWith("-")) {
- lineClass += "bg-red-900/30 text-red-400 border-red-500";
- } else {
- lineClass += "border-transparent";
- }
- return (
-
-
- {index + 1}
-
- {line}
-
- );
- });
- };
-
- const formattedContent = formatDiffContent(diffContent);
-
- return (
-
-
-
-
-
- Type:
-
-
- {type}
-
-
-
-
- Name:
-
-
- {name === "Deleted File" ? "Deleted File" : name}
-
-
- {commitMessage && (
-
-
- Commit Message:
-
-
- {commitMessage}
-
-
- )}
-
-
-
- Diff Content
-
-
- {formattedContent.length > 0 ? (
- formattedContent
- ) : (
-
- No differences found or file is empty.
-
- )}
-
-
-
-
- );
-};
-
-DiffModal.propTypes = {
- isOpen: PropTypes.bool.isRequired,
- onClose: PropTypes.func.isRequired,
- diffContent: PropTypes.string,
- type: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- commitMessage: PropTypes.string,
- title: PropTypes.string,
-};
-
-export default DiffModal;
diff --git a/frontend/src/components/settings/SettingsPage.jsx b/frontend/src/components/settings/SettingsPage.jsx
index 905504e..ef283a7 100644
--- a/frontend/src/components/settings/SettingsPage.jsx
+++ b/frontend/src/components/settings/SettingsPage.jsx
@@ -1,4 +1,4 @@
-import React, {useState, useEffect} from 'react';
+import React, { useState, useEffect } from 'react';
import {
getSettings,
getGitStatus,
@@ -6,58 +6,27 @@ import {
pushFiles,
revertFile,
pullBranch,
- getDiff,
- unlinkRepo,
checkDevMode
} from '../../api/api';
-import SettingsBranchModal from './SettingsBranchModal';
import {
- FileText,
- Code,
- AlertCircle,
- Plus,
- MinusCircle,
- Edit,
- GitBranch,
Loader,
- Eye,
- RotateCcw,
- Download,
- ArrowDown,
- ArrowUp,
- CheckCircle,
- File,
- Settings,
- Unlink
} from 'lucide-react';
+import SettingsBranchModal from './SettingsBranchModal';
import Alert from '../ui/Alert';
-import CommitSection from './CommitSection';
-import Tooltip from '../ui/Tooltip';
-import DiffModal from './DiffModal';
import ArrContainer from './arrs/ArrContainer';
import RepoContainer from './git/RepoContainer';
+import StatusContainer from './git/StatusContainer';
+import { statusLoadingMessages, noChangesMessages, getRandomMessage } from '../../utils/messages';
const SettingsPage = () => {
const [settings, setSettings] = useState(null);
- const [status, setStatus] = useState(null);
+ const [changes, setChanges] = useState(null);
const [isDevMode, setIsDevMode] = useState(false);
const [showBranchModal, setShowBranchModal] = useState(false);
const [loadingAction, setLoadingAction] = useState('');
- const [loadingStatus, setLoadingStatus] = useState(true);
- const [loadingMessage, setLoadingMessage] = useState('');
- const [commitMessage, setCommitMessage] = useState('');
- const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]);
- const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]);
- const [showDiffModal, setShowDiffModal] = useState(false);
- const [diffContent, setDiffContent] = useState('');
- const [currentChange, setCurrentChange] = useState(null);
- const [loadingDiff, setLoadingDiff] = useState(false);
- const [selectionType, setSelectionType] = useState(null);
- const [funMessage, setFunMessage] = useState('');
- const [sortConfig, setSortConfig] = useState({
- key: 'type',
- direction: 'descending'
- });
+ const [statusLoading, setStatusLoading] = useState(true);
+ const [statusLoadingMessage, setStatusLoadingMessage] = useState('');
+ const [noChangesMessage, setNoChangesMessage] = useState('');
useEffect(() => {
fetchSettings();
@@ -86,72 +55,19 @@ const SettingsPage = () => {
}
};
- const sortedChanges = changes => {
- if (!sortConfig.key) return changes;
-
- return [...changes].sort((a, b) => {
- if (a[sortConfig.key] < b[sortConfig.key]) {
- return sortConfig.direction === 'ascending' ? -1 : 1;
- }
- if (a[sortConfig.key] > b[sortConfig.key]) {
- return sortConfig.direction === 'ascending' ? 1 : -1;
- }
- return 0;
- });
- };
-
- const requestSort = key => {
- let direction = 'ascending';
- if (sortConfig.key === key && sortConfig.direction === 'ascending') {
- direction = 'descending';
- }
- setSortConfig({key, direction});
- };
-
- const SortableHeader = ({children, sortKey}) => {
- const isSorted = sortConfig.key === sortKey;
- return (
-
requestSort(sortKey)}>
-
- {children}
- {isSorted &&
- (sortConfig.direction === 'ascending' ? (
-
- ) : (
-
- ))}
-
-
- );
- };
-
const fetchGitStatus = async () => {
- setLoadingStatus(true);
- setLoadingMessage(getRandomLoadingMessage());
- setFunMessage(getRandomFunMessage());
+ setStatusLoading(true);
+ setStatusLoadingMessage(getRandomMessage(statusLoadingMessages));
+ setNoChangesMessage(getRandomMessage(noChangesMessages));
try {
const result = await getGitStatus();
- console.log(
- '================ Git Status Response ================'
- );
- console.log(JSON.stringify(result, null, 2));
- console.log(
- '======================================================'
- );
-
if (result.success) {
- setStatus({
+ setChanges({
...result.data,
- outgoing_changes: Array.isArray(
- result.data.outgoing_changes
- )
+ outgoing_changes: Array.isArray(result.data.outgoing_changes)
? result.data.outgoing_changes
: [],
- incoming_changes: Array.isArray(
- result.data.incoming_changes
- )
+ incoming_changes: Array.isArray(result.data.incoming_changes)
? result.data.incoming_changes
: []
});
@@ -160,206 +76,16 @@ const SettingsPage = () => {
console.error('Error fetching Git status:', error);
Alert.error('Failed to fetch Git status');
} finally {
- setLoadingStatus(false);
+ setStatusLoading(false);
}
};
- const getRandomFunMessage = () => {
- const funMessages = [
- 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐งต๐',
- 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐',
- "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐ง",
- 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐พ',
- "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐ช()",
- 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐๐ค',
- "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐ข๐",
- "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐งนโจ",
- "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐ค๐งฉ",
- 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ฌ๐'
- ];
- return funMessages[Math.floor(Math.random() * funMessages.length)];
- };
-
- const renderChangeTable = (changes, title, icon, isIncoming) => (
-
-
- {icon}
-
- {isIncoming
- ? title
- : isDevMode
- ? 'Outgoing Changes'
- : 'Local Changes'}{' '}
- ({changes.length})
-
-
-
-
- );
-
- const getStageButtonTooltip = () => {
- if (selectionType === 'staged') {
- return 'These files are already staged';
- }
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to stage';
- }
- return 'Stage selected files';
- };
-
- const getCommitButtonTooltip = () => {
- if (selectionType === 'unstaged') {
- return 'You can only commit staged files';
- }
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to commit';
- }
- if (!commitMessage.trim()) {
- return 'Enter a commit message';
- }
- return 'Commit selected files';
- };
-
- const getRevertButtonTooltip = () => {
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to revert';
- }
- return 'Revert selected files';
- };
-
- const handleViewDiff = async change => {
- setLoadingDiff(true);
- try {
- const response = await getDiff(change.file_path);
- console.log('Diff response:', response); // Add this line to log the response
- if (response.success) {
- console.log('Diff content:', response.diff); // Add this line to log the diff content
- setDiffContent(response.diff);
- setCurrentChange(change);
- setShowDiffModal(true);
- } else {
- Alert.error(response.error);
- }
- } catch (error) {
- Alert.error(
- 'An unexpected error occurred while fetching the diff.'
- );
- console.error('Error fetching diff:', error);
- } finally {
- setLoadingDiff(false);
- setLoadingAction('');
- }
- };
-
- const handleStageSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to stage.');
- return;
- }
-
+ const handleStageSelectedChanges = async (selectedChanges) => {
setLoadingAction('stage_selected');
try {
- const response = await addFiles(selectedOutgoingChanges);
+ const response = await addFiles(selectedChanges);
if (response.success) {
await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after staging
Alert.success(response.message);
} else {
Alert.error(response.error);
@@ -372,90 +98,49 @@ const SettingsPage = () => {
}
};
- const handleCommitSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to commit.');
- return;
- }
-
- if (!commitMessage.trim()) {
- Alert.warning('Please enter a commit message.');
- return;
- }
-
+ const handleCommitSelectedChanges = async (selectedChanges, commitMessage) => {
setLoadingAction('commit_selected');
try {
- const response = await pushFiles(
- selectedOutgoingChanges,
- commitMessage
- );
+ const response = await pushFiles(selectedChanges, commitMessage);
if (response.success) {
await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after committing
- setCommitMessage('');
Alert.success(response.message);
} else {
Alert.error(response.error);
}
} catch (error) {
- Alert.error(
- 'An unexpected error occurred while committing changes.'
- );
+ Alert.error('An unexpected error occurred while committing changes.');
console.error('Error committing changes:', error);
} finally {
setLoadingAction('');
}
};
- const handleRevertSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to revert.');
- return;
- }
-
+ const handleRevertSelectedChanges = async (selectedChanges) => {
setLoadingAction('revert_selected');
try {
- const response = await Promise.all(
- selectedOutgoingChanges.map(filePath => revertFile(filePath))
- );
+ const response = await Promise.all(selectedChanges.map(filePath => revertFile(filePath)));
const allSuccessful = response.every(res => res.success);
if (allSuccessful) {
await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after reverting
- Alert.success(
- 'Selected changes have been reverted successfully.'
- );
+ Alert.success('Selected changes have been reverted successfully.');
} else {
- Alert.error(
- 'Some changes could not be reverted. Please try again.'
- );
+ Alert.error('Some changes could not be reverted. Please try again.');
}
} catch (error) {
- Alert.error(
- 'An unexpected error occurred while reverting changes.'
- );
+ Alert.error('An unexpected error occurred while reverting changes.');
console.error('Error reverting changes:', error);
} finally {
setLoadingAction('');
}
};
- const handlePullSelectedChanges = async () => {
- if (selectedIncomingChanges.length === 0) {
- Alert.warning('Please select at least one change to pull.');
- return;
- }
-
+ const handlePullSelectedChanges = async (selectedChanges) => {
setLoadingAction('pull_changes');
try {
- // You would need to update your backend to handle pulling specific files
- const response = await pullBranch(
- status.branch,
- selectedIncomingChanges
- );
+ const response = await pullBranch(changes.branch, selectedChanges);
if (response.success) {
await fetchGitStatus();
- setSelectedIncomingChanges([]); // Clear the selected changes after pulling
Alert.success(response.message);
} else {
Alert.error(response.error);
@@ -468,101 +153,11 @@ const SettingsPage = () => {
}
};
- const handleSelectChange = (filePath, isIncoming) => {
- if (isIncoming) {
- setSelectedIncomingChanges(prevSelected => {
- if (prevSelected.includes(filePath)) {
- return prevSelected.filter(path => path !== filePath);
- } else {
- return [...prevSelected, filePath];
- }
- });
- } else {
- const change = status.outgoing_changes.find(
- c => c.file_path === filePath
- );
- const isStaged = change.staged;
-
- setSelectedOutgoingChanges(prevSelected => {
- if (prevSelected.includes(filePath)) {
- const newSelection = prevSelected.filter(
- path => path !== filePath
- );
- if (newSelection.length === 0) setSelectionType(null);
- return newSelection;
- } else {
- if (
- prevSelected.length === 0 ||
- (isStaged && selectionType === 'staged') ||
- (!isStaged && selectionType === 'unstaged')
- ) {
- setSelectionType(isStaged ? 'staged' : 'unstaged');
- return [...prevSelected, filePath];
- } else {
- return prevSelected;
- }
- }
- });
- }
- };
-
- const loadingMessages = [
- "Checking for changes... don't blink!",
- 'Syncing with the mothership...',
- 'Peeking under the hood...',
- 'Counting bits and bytes...',
- 'Scanning for modifications...',
- 'Looking for new stuff...',
- 'Comparing local and remote...',
- "Checking your project's pulse...",
- "Analyzing your code's mood...",
- "Reading the project's diary..."
- ];
-
- const getRandomLoadingMessage = () => {
- return loadingMessages[
- Math.floor(Math.random() * loadingMessages.length)
- ];
- };
-
- const getStatusIcon = status => {
- switch (status) {
- case 'Untracked':
- return
;
- case 'Staged (New)':
- return
;
- case 'Staged (Modified)':
- case 'Modified':
- return
;
- case 'Deleted':
- return
;
- case 'Deleted (Staged)':
- return
;
- case 'Renamed':
- return
;
- default:
- return
;
- }
- };
-
- const getTypeIcon = type => {
- switch (type) {
- case 'Regex Pattern':
- return
;
- case 'Custom Format':
- return
;
- case 'Quality Profile':
- return
;
- default:
- return
;
- }
- };
-
return (
-
- Git Repository Settings
-
+
+ Git Settings
+
{isDevMode ? 'Dev Mode: Enabled' : 'Dev Mode: Disabled'}
@@ -574,248 +169,48 @@ const SettingsPage = () => {
/>
{settings && (
-
-
-
- Git Status
-
- {loadingStatus ? (
-
-
-
- {loadingMessage}
-
-
- ) : (
- status && (
- <>
-
-
-
-
- Current Branch: {status.branch}
-
-
-
- setShowBranchModal(true)
- }
- className='flex items-center px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors duration-200 ease-in-out text-xs'>
-
- View Branches
-
-
-
- {status.incoming_changes.length > 0 &&
- renderChangeTable(
- status.incoming_changes,
- 'Incoming Changes',
-
,
- true
- )}
- {status.outgoing_changes.length > 0 &&
- renderChangeTable(
- status.outgoing_changes,
- 'Outgoing Changes',
-
,
- false
- )}
-
0
- }
- funMessage={funMessage}
- isDevMode={isDevMode}
- />
- {/* Buttons Below Commit Section */}
-
- {isDevMode && (
- <>
- {/* Stage button */}
- {selectedOutgoingChanges.length >
- 0 &&
- selectionType !==
- 'staged' && (
-
-
- {loadingAction ===
- 'stage_selected' ? (
-
- ) : (
-
- )}
- Stage Selected
-
-
- )}
-
- {/* Commit button */}
- {selectedOutgoingChanges.length >
- 0 &&
- commitMessage.trim() &&
- selectionType !==
- 'unstaged' && (
-
-
- {loadingAction ===
- 'commit_selected' ? (
-
- ) : (
-
- )}
- Commit Selected
-
-
- )}
- >
- )}
-
- {/* Revert button (moved outside isDevMode check) */}
- {selectedOutgoingChanges.length > 0 && (
-
-
- {loadingAction ===
- 'revert_selected' ? (
-
- ) : (
-
- )}
- Revert Selected
-
-
- )}
-
- {/* Pull button (always enabled) */}
- {selectedIncomingChanges.length > 0 && (
-
-
- {loadingAction ===
- 'pull_changes' ? (
-
- ) : (
-
- )}
- Pull Selected
-
-
- )}
-
- >
- )
- )}
-
+
+ {statusLoading ? (
+
+
+ {statusLoadingMessage}
+
+ ) : changes && (changes.incoming_changes.length > 0 || changes.outgoing_changes.length > 0) ? (
+
setShowBranchModal(true)}
+ onStageSelected={handleStageSelectedChanges}
+ onCommitSelected={handleCommitSelectedChanges}
+ onRevertSelected={handleRevertSelectedChanges}
+ onPullSelected={handlePullSelectedChanges}
+ loadingAction={loadingAction}
+ />
+ ) : (
+
+ {noChangesMessage}
+
+ )}
)}
-
+
+
Arr Management
- {settings && status && (
+
+ {settings && changes && (
setShowBranchModal(false)}
repoUrl={settings.gitRepo}
- currentBranch={status.branch}
+ currentBranch={changes.branch}
onBranchChange={fetchGitStatus}
isDevMode={isDevMode}
/>
)}
- {showDiffModal && currentChange && (
- setShowDiffModal(false)}
- diffContent={diffContent}
- type={currentChange.type}
- name={currentChange.name}
- commitMessage={currentChange.commit_message}
- isDevMode={isDevMode}
- />
- )}
);
};
-export default SettingsPage;
+export default SettingsPage;
\ No newline at end of file
diff --git a/frontend/src/components/settings/git/ChangeRow.jsx b/frontend/src/components/settings/git/ChangeRow.jsx
new file mode 100644
index 0000000..f09e72d
--- /dev/null
+++ b/frontend/src/components/settings/git/ChangeRow.jsx
@@ -0,0 +1,148 @@
+import React, {useState} from 'react';
+import {
+ Eye,
+ Loader,
+ Plus,
+ MinusCircle,
+ Edit,
+ GitBranch,
+ AlertCircle,
+ Code,
+ FileText,
+ Settings,
+ File
+} from 'lucide-react';
+import Tooltip from '../../ui/Tooltip';
+import {getDiff} from '../../../api/api';
+import Alert from '../../ui/Alert';
+import Diff from './modal/ViewDiff';
+
+const ChangeRow = ({change, isSelected, onSelect, isIncoming, isDevMode}) => {
+ const [loadingDiff, setLoadingDiff] = useState(false);
+ const [showDiff, setShowDiff] = useState(false);
+ const [diffContent, setDiffContent] = useState('');
+
+ const getStatusIcon = status => {
+ switch (status) {
+ case 'Untracked':
+ return ;
+ case 'Staged (New)':
+ return ;
+ case 'Staged (Modified)':
+ case 'Modified':
+ return ;
+ case 'Deleted':
+ return ;
+ case 'Deleted (Staged)':
+ return ;
+ case 'Renamed':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getTypeIcon = type => {
+ switch (type) {
+ case 'Regex Pattern':
+ return ;
+ case 'Custom Format':
+ return ;
+ case 'Quality Profile':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const handleViewDiff = async () => {
+ setLoadingDiff(true);
+ try {
+ const response = await getDiff(change.file_path);
+ if (response.success) {
+ setDiffContent(response.diff);
+ setShowDiff(true);
+ } else {
+ Alert.error(response.error);
+ }
+ } catch (error) {
+ Alert.error(
+ 'An unexpected error occurred while fetching the diff.'
+ );
+ console.error('Error fetching diff:', error);
+ } finally {
+ setLoadingDiff(false);
+ }
+ };
+
+ return (
+ <>
+ onSelect(change.file_path)}>
+
+
+ {getStatusIcon(change.status)}
+
+ {change.staged
+ ? `${change.status} (Staged)`
+ : change.status}
+
+
+
+
+
+ {getTypeIcon(change.type)}
+ {change.type}
+
+
+
+ {change.name || 'Unnamed'}
+
+
+
+ {
+ e.stopPropagation();
+ handleViewDiff();
+ }}
+ className='flex items-center justify-center px-2 py-1 bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors text-xs'
+ style={{width: '100%'}}>
+ {loadingDiff ? (
+
+ ) : (
+ <>
+
+ View Diff
+ >
+ )}
+
+
+
+
+ e.stopPropagation()}
+ disabled={!isIncoming && change.staged}
+ />
+
+
+ {showDiff && (
+ setShowDiff(false)}
+ diffContent={diffContent}
+ type={change.type}
+ name={change.name}
+ commitMessage={change.commit_message}
+ isDevMode={isDevMode}
+ />
+ )}
+ >
+ );
+};
+
+export default ChangeRow;
diff --git a/frontend/src/components/settings/git/ChangeTable.jsx b/frontend/src/components/settings/git/ChangeTable.jsx
new file mode 100644
index 0000000..8d62d91
--- /dev/null
+++ b/frontend/src/components/settings/git/ChangeTable.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import {ArrowDown, ArrowUp} from 'lucide-react';
+import ChangeRow from './ChangeRow';
+
+const ChangeTable = ({
+ changes,
+ title,
+ icon,
+ isIncoming,
+ selectedChanges,
+ onSelectChange,
+ sortConfig,
+ onRequestSort,
+ isDevMode
+}) => {
+ const sortedChanges = changesArray => {
+ if (!sortConfig.key) return changesArray;
+
+ return [...changesArray].sort((a, b) => {
+ if (a[sortConfig.key] < b[sortConfig.key]) {
+ return sortConfig.direction === 'ascending' ? -1 : 1;
+ }
+ if (a[sortConfig.key] > b[sortConfig.key]) {
+ return sortConfig.direction === 'ascending' ? 1 : -1;
+ }
+ return 0;
+ });
+ };
+
+ const SortableHeader = ({children, sortKey}) => {
+ const isSorted = sortConfig.key === sortKey;
+ return (
+ onRequestSort(sortKey)}>
+
+ {children}
+ {isSorted &&
+ (sortConfig.direction === 'ascending' ? (
+
+ ) : (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+
+ {icon}
+
+ {isIncoming
+ ? title
+ : isDevMode
+ ? 'Outgoing Changes'
+ : 'Local Changes'}{' '}
+ ({changes.length})
+
+
+
+
+
+
+
+ Status
+
+ Type
+ Name
+
+ Actions
+
+
+ Select
+
+
+
+
+ {sortedChanges(changes).map((change, index) => (
+
+ onSelectChange(filePath, isIncoming)
+ }
+ isIncoming={isIncoming}
+ isDevMode={isDevMode}
+ />
+ ))}
+
+
+
+
+ );
+};
+
+export default ChangeTable;
diff --git a/frontend/src/components/settings/git/RepoContainer.jsx b/frontend/src/components/settings/git/RepoContainer.jsx
index 8b5253f..98cee29 100644
--- a/frontend/src/components/settings/git/RepoContainer.jsx
+++ b/frontend/src/components/settings/git/RepoContainer.jsx
@@ -1,22 +1,22 @@
-import React, {useState} from 'react';
-import {Loader, Unlink, Link} from 'lucide-react';
+import React, { useState } from 'react';
+import { Loader, Unlink, Link } from 'lucide-react';
import Tooltip from '../../ui/Tooltip';
-import {unlinkRepo, getSettings} from '../../../api/api';
+import { unlinkRepo, getSettings } from '../../../api/api';
import Alert from '../../ui/Alert';
import LinkRepo from './modal/LinkRepo';
import UnlinkRepo from './modal/UnlinkRepo';
-const RepoContainer = ({settings, setSettings, fetchGitStatus}) => {
+const RepoContainer = ({ settings, setSettings, fetchGitStatus }) => {
const [loadingAction, setLoadingAction] = useState('');
const [showLinkModal, setShowLinkModal] = useState(false);
const [showUnlinkRepo, setShowUnlinkRepo] = useState(false);
- const handleLinkRepo = async () => {
+ const handleLinkRepo = () => {
setLoadingAction('link_repo');
setShowLinkModal(true);
};
- const handleUnlinkRepo = async removeFiles => {
+ const handleUnlinkRepo = async (removeFiles) => {
setLoadingAction('unlink_repo');
try {
const response = await unlinkRepo(removeFiles);
@@ -47,23 +47,17 @@ const RepoContainer = ({settings, setSettings, fetchGitStatus}) => {
}
};
- if (!settings) {
- return (
- <>
-
-
- Link Repository
-
- setShowLinkModal(false)}
- onSubmit={onLinkSubmit}
- />
- >
- );
- }
+ // Handler to close the LinkRepo modal and reset loadingAction
+ const closeLinkModal = () => {
+ setShowLinkModal(false);
+ setLoadingAction('');
+ };
+
+ // Handler to close the UnlinkRepo modal and reset loadingAction
+ const closeUnlinkModal = () => {
+ setShowUnlinkRepo(false);
+ setLoadingAction('');
+ };
return (
@@ -71,37 +65,43 @@ const RepoContainer = ({settings, setSettings, fetchGitStatus}) => {
-
+
setShowUnlinkRepo(true)}
- className='flex items-center px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors duration-200 ease-in-out text-sm font-medium shadow-sm'
- disabled={loadingAction === 'unlink_repo'}>
- {loadingAction === 'unlink_repo' ? (
-
- ) : (
+ onClick={settings ? () => setShowUnlinkRepo(true) : handleLinkRepo}
+ className={`flex items-center px-4 py-2 ${settings ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'} text-white rounded-md transition-colors duration-200 ease-in-out text-sm font-medium shadow-sm`}
+ disabled={loadingAction !== ''}>
+ {settings ? (
+ ) : (
+
)}
- Unlink
+ {settings ? 'Unlink' : 'Link Repository'}
+
setShowUnlinkRepo(false)}
+ onClose={closeUnlinkModal}
onSubmit={handleUnlinkRepo}
/>
diff --git a/frontend/src/components/settings/git/StatusContainer.jsx b/frontend/src/components/settings/git/StatusContainer.jsx
new file mode 100644
index 0000000..4547834
--- /dev/null
+++ b/frontend/src/components/settings/git/StatusContainer.jsx
@@ -0,0 +1,274 @@
+import React, {useState} from 'react';
+import {
+ GitBranch,
+ Loader,
+ RotateCcw,
+ Download,
+ CheckCircle,
+ Plus,
+ Eye
+} from 'lucide-react';
+import ChangeTable from './ChangeTable';
+import Tooltip from '../../ui/Tooltip';
+import CommitSection from '../CommitSection';
+
+const StatusContainer = ({
+ status,
+ isDevMode,
+ onViewBranches,
+ onStageSelected,
+ onCommitSelected,
+ onRevertSelected,
+ onPullSelected,
+ loadingAction
+}) => {
+ const [sortConfig, setSortConfig] = useState({
+ key: 'type',
+ direction: 'ascending'
+ });
+ const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]);
+ const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]);
+ const [commitMessage, setCommitMessage] = useState('');
+ const [selectionType, setSelectionType] = useState(null);
+
+ const requestSort = key => {
+ let direction = 'ascending';
+ if (sortConfig.key === key && sortConfig.direction === 'ascending') {
+ direction = 'descending';
+ }
+ setSortConfig({key, direction});
+ };
+
+ const handleSelectChange = (filePath, isIncoming) => {
+ if (isIncoming) {
+ setSelectedIncomingChanges(prevSelected => {
+ if (prevSelected.includes(filePath)) {
+ return prevSelected.filter(path => path !== filePath);
+ } else {
+ return [...prevSelected, filePath];
+ }
+ });
+ } else {
+ const change = status.outgoing_changes.find(
+ c => c.file_path === filePath
+ );
+ const isStaged = change.staged;
+
+ setSelectedOutgoingChanges(prevSelected => {
+ if (prevSelected.includes(filePath)) {
+ const newSelection = prevSelected.filter(
+ path => path !== filePath
+ );
+ if (newSelection.length === 0) setSelectionType(null);
+ return newSelection;
+ } else {
+ if (
+ prevSelected.length === 0 ||
+ (isStaged && selectionType === 'staged') ||
+ (!isStaged && selectionType === 'unstaged')
+ ) {
+ setSelectionType(isStaged ? 'staged' : 'unstaged');
+ return [...prevSelected, filePath];
+ } else {
+ return prevSelected;
+ }
+ }
+ });
+ }
+ };
+
+ const getStageButtonTooltip = () => {
+ if (selectionType === 'staged') {
+ return 'These files are already staged';
+ }
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to stage';
+ }
+ return 'Stage selected files';
+ };
+
+ const getCommitButtonTooltip = () => {
+ if (selectionType === 'unstaged') {
+ return 'You can only commit staged files';
+ }
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to commit';
+ }
+ if (!commitMessage.trim()) {
+ return 'Enter a commit message';
+ }
+ return 'Commit selected files';
+ };
+
+ const getRevertButtonTooltip = () => {
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to revert';
+ }
+ return 'Revert selected files';
+ };
+
+ return (
+
+
+ Git Status
+
+
+
+
+
+ Current Branch: {status.branch}
+
+
+
+
+ View Branches
+
+
+
+ {status.incoming_changes.length > 0 && (
+
+ }
+ isIncoming={true}
+ selectedChanges={selectedIncomingChanges}
+ onSelectChange={handleSelectChange}
+ sortConfig={sortConfig}
+ onRequestSort={requestSort}
+ isDevMode={isDevMode}
+ />
+ )}
+
+ {status.outgoing_changes.length > 0 && (
+
+ }
+ isIncoming={false}
+ selectedChanges={selectedOutgoingChanges}
+ onSelectChange={handleSelectChange}
+ sortConfig={sortConfig}
+ onRequestSort={requestSort}
+ isDevMode={isDevMode}
+ />
+ )}
+
+
0}
+ isDevMode={isDevMode}
+ />
+
+ {/* Action Buttons */}
+
+ {isDevMode && (
+ <>
+ {selectedOutgoingChanges.length > 0 &&
+ selectionType !== 'staged' && (
+
+
+ onStageSelected(
+ selectedOutgoingChanges
+ )
+ }
+ className='flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors duration-200 ease-in-out text-xs'
+ disabled={
+ loadingAction === 'stage_selected'
+ }>
+ {loadingAction === 'stage_selected' ? (
+
+ ) : (
+
+ )}
+ Stage Selected
+
+
+ )}
+
+ {selectedOutgoingChanges.length > 0 &&
+ commitMessage.trim() &&
+ selectionType !== 'unstaged' && (
+
+
+ onCommitSelected(
+ selectedOutgoingChanges,
+ commitMessage
+ )
+ }
+ className='flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors duration-200 ease-in-out text-xs'
+ disabled={
+ loadingAction === 'commit_selected'
+ }>
+ {loadingAction === 'commit_selected' ? (
+
+ ) : (
+
+ )}
+ Commit Selected
+
+
+ )}
+ >
+ )}
+
+ {selectedOutgoingChanges.length > 0 && (
+
+
+ onRevertSelected(selectedOutgoingChanges)
+ }
+ className='flex items-center px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors duration-200 ease-in-out text-xs'
+ disabled={loadingAction === 'revert_selected'}>
+ {loadingAction === 'revert_selected' ? (
+
+ ) : (
+
+ )}
+ Revert Selected
+
+
+ )}
+
+ {selectedIncomingChanges.length > 0 && (
+
+
+ onPullSelected(selectedIncomingChanges)
+ }
+ className='flex items-center px-4 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 transition-colors duration-200 ease-in-out text-xs'
+ disabled={loadingAction === 'pull_changes'}>
+ {loadingAction === 'pull_changes' ? (
+
+ ) : (
+
+ )}
+ Pull Selected
+
+
+ )}
+
+
+ );
+};
+
+export default StatusContainer;
diff --git a/frontend/src/components/settings/git/modal/LinkRepo.jsx b/frontend/src/components/settings/git/modal/LinkRepo.jsx
index f663a70..af62642 100644
--- a/frontend/src/components/settings/git/modal/LinkRepo.jsx
+++ b/frontend/src/components/settings/git/modal/LinkRepo.jsx
@@ -1,8 +1,8 @@
import React, {useState} from 'react';
-import Modal from '../../ui/Modal';
+import Modal from '../../../ui/Modal';
import {Loader} from 'lucide-react';
-import {cloneRepo} from '../../../api/api';
-import Alert from '../../ui/Alert';
+import {cloneRepo} from '../../../../api/api';
+import Alert from '../../../ui/Alert';
const LinkRepo = ({isOpen, onClose, onSubmit}) => {
const [gitRepo, setGitRepo] = useState('');
diff --git a/frontend/src/components/settings/git/modal/ViewDiff.jsx b/frontend/src/components/settings/git/modal/ViewDiff.jsx
new file mode 100644
index 0000000..be7d1ea
--- /dev/null
+++ b/frontend/src/components/settings/git/modal/ViewDiff.jsx
@@ -0,0 +1,100 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Modal from '../../../ui/Modal';
+
+const Diff = ({
+ isOpen,
+ onClose,
+ diffContent,
+ type,
+ name,
+ commitMessage,
+ title = 'View Diff'
+}) => {
+ const formatDiffContent = content => {
+ if (!content) return [];
+ return content.split('\n').map((line, index) => {
+ let lineClass = 'py-1 pl-4 border-l-2 ';
+ if (line.startsWith('+')) {
+ lineClass += 'bg-green-900/30 text-green-400 border-green-500';
+ } else if (line.startsWith('-')) {
+ lineClass += 'bg-red-900/30 text-red-400 border-red-500';
+ } else {
+ lineClass += 'border-transparent';
+ }
+ return (
+
+
+ {index + 1}
+
+
+ {line}
+
+
+ );
+ });
+ };
+
+ const formattedContent = formatDiffContent(diffContent);
+
+ return (
+
+
+
+
+
+ Type:
+
+
+ {type}
+
+
+
+
+ Name:
+
+
+ {name === 'Deleted File' ? 'Deleted File' : name}
+
+
+ {commitMessage && (
+
+
+ Commit Message:
+
+
+ {commitMessage}
+
+
+ )}
+
+
+
+ Diff Content
+
+
+ {formattedContent.length > 0 ? (
+ formattedContent
+ ) : (
+
+ No differences found or file is empty.
+
+ )}
+
+
+
+
+ );
+};
+
+Diff.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ diffContent: PropTypes.string,
+ type: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ commitMessage: PropTypes.string,
+ title: PropTypes.string
+};
+
+export default Diff;
diff --git a/frontend/src/utils/messages.js b/frontend/src/utils/messages.js
new file mode 100644
index 0000000..6100989
--- /dev/null
+++ b/frontend/src/utils/messages.js
@@ -0,0 +1,31 @@
+// messages.js
+
+export const statusLoadingMessages = [
+ "Checking for changes... don't blink!",
+ 'Syncing with the mothership...',
+ 'Peeking under the hood...',
+ 'Counting bits and bytes...',
+ 'Scanning for modifications...',
+ 'Looking for new stuff...',
+ 'Comparing local and remote...',
+ "Checking your project's pulse...",
+ "Analyzing your code's mood...",
+ "Reading the project's diary..."
+];
+
+export const noChangesMessages = [
+ 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐งต๐',
+ 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐',
+ "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐ง",
+ 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐พ',
+ "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐ช()",
+ 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐๐ค',
+ "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐ข๐",
+ "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐งนโจ",
+ "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐ค๐งฉ",
+ 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ฌ๐'
+];
+
+export const getRandomMessage = (messages) => {
+ return messages[Math.floor(Math.random() * messages.length)];
+};