mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-03-15 11:11:27 -07:00
Fix/open app (#658)
* fix: registry-js not copied * Update wiki * Update getWorkspaceMenuTemplate.ts * fix: tiddlers\新条目.tid become "tiddlers/\346\226\260\346\235\241\347\233\256.tid" in git log * fix: git can't show and discard newly added or deleted files * refactor: duplicate code * lint * fix: type
This commit is contained in:
parent
45e3f76da1
commit
a674cd269f
17 changed files with 386 additions and 79 deletions
|
|
@ -13,6 +13,7 @@ import Typography from '@mui/material/Typography';
|
|||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { getFileStatusStyles, type GitFileStatus } from './fileStatusStyles';
|
||||
import type { GitLogEntry } from './types';
|
||||
|
||||
const Panel = styled(Box)`
|
||||
|
|
@ -53,6 +54,17 @@ const ActionsWrapper = styled(Box)`
|
|||
gap: 12px;
|
||||
`;
|
||||
|
||||
const FileStatusBadge = styled(Box)<{ $status?: GitFileStatus }>`
|
||||
display: inline-block;
|
||||
font-size: 0.6rem;
|
||||
padding: 1px 4px;
|
||||
margin-right: 4px;
|
||||
border-radius: 2px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
${({ $status, theme }) => getFileStatusStyles($status, theme)}
|
||||
`;
|
||||
|
||||
interface ICommitDetailsPanelProps {
|
||||
commit: GitLogEntry | null;
|
||||
isLatestCommit?: boolean;
|
||||
|
|
@ -241,13 +253,18 @@ export function CommitDetailsPanel(
|
|||
{fileChanges.map((file, index) => (
|
||||
<ListItem key={index} disablePadding>
|
||||
<ListItemButton
|
||||
selected={file === selectedFile}
|
||||
selected={file.path === selectedFile}
|
||||
onClick={() => {
|
||||
onFileSelect?.(file === selectedFile ? null : file);
|
||||
onFileSelect?.(file.path === selectedFile ? null : file.path);
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={file}
|
||||
primary={
|
||||
<>
|
||||
<FileStatusBadge $status={file.status}>{file.status.charAt(0)}</FileStatusBadge>
|
||||
{file.path}
|
||||
</>
|
||||
}
|
||||
slotProps={{
|
||||
primary: {
|
||||
variant: 'body2',
|
||||
|
|
|
|||
|
|
@ -136,9 +136,10 @@ interface IFileDiffPanelProps {
|
|||
commitHash: string;
|
||||
filePath: string | null;
|
||||
onDiscardSuccess?: () => void;
|
||||
showSnackbar?: (message: string, severity: 'success' | 'error' | 'info') => void;
|
||||
}
|
||||
|
||||
export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileDiffPanelProps): React.JSX.Element {
|
||||
export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess, showSnackbar: showSnackbarFromParent }: IFileDiffPanelProps): React.JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const [diff, setDiff] = useState<string>('');
|
||||
const [fileContent, setFileContent] = useState<string>('');
|
||||
|
|
@ -153,6 +154,9 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
|
|||
const [isLoadingFullDiff, setIsLoadingFullDiff] = useState(false);
|
||||
const [isLoadingFullContent, setIsLoadingFullContent] = useState(false);
|
||||
|
||||
// Use parent's showSnackbar if provided, otherwise create local one
|
||||
const showSnackbar = showSnackbarFromParent ?? (() => {});
|
||||
|
||||
const getWorkspace = async () => {
|
||||
const meta = window.meta();
|
||||
const workspaceID = (meta as { workspaceID?: string }).workspaceID;
|
||||
|
|
@ -192,11 +196,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
|
|||
|
||||
try {
|
||||
await window.service.git.discardFileChanges(workspace.wikiFolderLocation, filePath);
|
||||
showSnackbar(t('GitLog.DiscardSuccess'), 'success');
|
||||
// Clear selection and trigger refresh
|
||||
onDiscardSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to discard changes:', error);
|
||||
// TODO: Show error message
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
showSnackbar(t('GitLog.DiscardFailed') + ': ' + errorMessage, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -207,10 +213,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
|
|||
|
||||
try {
|
||||
await window.service.git.addToGitignore(workspace.wikiFolderLocation, filePath);
|
||||
// TODO: Show success message
|
||||
showSnackbar(t('GitLog.IgnoreSuccess'), 'success');
|
||||
// Trigger refresh after adding to .gitignore
|
||||
onDiscardSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to add to .gitignore:', error);
|
||||
// TODO: Show error message
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
showSnackbar(t('GitLog.IgnoreFailed') + ': ' + errorMessage, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -221,10 +230,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
|
|||
|
||||
try {
|
||||
await window.service.git.addToGitignore(workspace.wikiFolderLocation, `*.${fileExtension}`);
|
||||
// TODO: Show success message
|
||||
showSnackbar(t('GitLog.IgnoreSuccess'), 'success');
|
||||
// Trigger refresh after adding to .gitignore
|
||||
onDiscardSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to add extension to .gitignore:', error);
|
||||
// TODO: Show error message
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
showSnackbar(t('GitLog.IgnoreFailed') + ': ' + errorMessage, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -235,13 +247,13 @@ export function FileDiffPanel({ commitHash, filePath, onDiscardSuccess }: IFileD
|
|||
|
||||
const fullPath = `${workspace.wikiFolderLocation}/${filePath}`;
|
||||
await navigator.clipboard.writeText(fullPath);
|
||||
// TODO: Show success message
|
||||
showSnackbar(t('GitLog.CopySuccess'), 'success');
|
||||
};
|
||||
|
||||
const handleCopyRelativePath = async () => {
|
||||
if (!filePath) return;
|
||||
await navigator.clipboard.writeText(filePath);
|
||||
// TODO: Show success message
|
||||
showSnackbar(t('GitLog.CopySuccess'), 'success');
|
||||
};
|
||||
|
||||
const handleShowInExplorer = async () => {
|
||||
|
|
|
|||
61
src/windows/GitLog/fileStatusStyles.ts
Normal file
61
src/windows/GitLog/fileStatusStyles.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import type { Theme } from '@mui/material/styles';
|
||||
import type { GitFileStatus } from '../../services/git/interface';
|
||||
|
||||
// Re-export for convenience
|
||||
export type { GitFileStatus };
|
||||
|
||||
/**
|
||||
* Get styled CSS for file status badge/chip based on the status and theme
|
||||
*/
|
||||
export function getFileStatusStyles(status: GitFileStatus | undefined, theme: Theme): string {
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
||||
switch (status) {
|
||||
case 'added':
|
||||
case 'untracked':
|
||||
return isDark
|
||||
? `
|
||||
background-color: rgba(46, 160, 67, 0.3);
|
||||
color: #7ee787;
|
||||
`
|
||||
: `
|
||||
background-color: rgba(46, 160, 67, 0.2);
|
||||
color: #116329;
|
||||
`;
|
||||
case 'deleted':
|
||||
return isDark
|
||||
? `
|
||||
background-color: rgba(248, 81, 73, 0.3);
|
||||
color: #ffa198;
|
||||
`
|
||||
: `
|
||||
background-color: rgba(248, 81, 73, 0.2);
|
||||
color: #82071e;
|
||||
`;
|
||||
case 'modified':
|
||||
return isDark
|
||||
? `
|
||||
background-color: rgba(187, 128, 9, 0.3);
|
||||
color: #f0b83f;
|
||||
`
|
||||
: `
|
||||
background-color: rgba(187, 128, 9, 0.2);
|
||||
color: #7d4e00;
|
||||
`;
|
||||
case 'renamed':
|
||||
return isDark
|
||||
? `
|
||||
background-color: rgba(56, 139, 253, 0.3);
|
||||
color: #79c0ff;
|
||||
`
|
||||
: `
|
||||
background-color: rgba(56, 139, 253, 0.2);
|
||||
color: #0969da;
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
background-color: ${theme.palette.action.hover};
|
||||
color: ${theme.palette.text.secondary};
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Helmet } from '@dr.pogodin/react-helmet';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Container from '@mui/material/Container';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Table from '@mui/material/Table';
|
||||
|
|
@ -21,6 +23,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import { CommitDetailsPanel } from './CommitDetailsPanel';
|
||||
import { CustomGitTooltip } from './CustomGitTooltip';
|
||||
import { FileDiffPanel } from './FileDiffPanel';
|
||||
import { getFileStatusStyles, type GitFileStatus } from './fileStatusStyles';
|
||||
import type { GitLogEntry } from './types';
|
||||
import { useCommitDetails } from './useCommitDetails';
|
||||
import { useGitLogData } from './useGitLogData';
|
||||
|
|
@ -108,18 +111,18 @@ const LoadingContainer = styled(Box)`
|
|||
height: 100%;
|
||||
`;
|
||||
|
||||
const FileChip = styled(Box)`
|
||||
const FileChip = styled(Box)<{ $status?: GitFileStatus }>`
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
font-family: monospace;
|
||||
padding: 2px 4px;
|
||||
margin: 2px;
|
||||
background-color: ${({ theme }) => theme.palette.action.hover};
|
||||
border-radius: 3px;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
${({ $status, theme }) => getFileStatusStyles($status, theme)}
|
||||
`;
|
||||
|
||||
interface ICommitTableRowProps {
|
||||
|
|
@ -158,10 +161,10 @@ function CommitTableRow({ commit, selected, commitDate, onSelect }: ICommitTable
|
|||
<TableCell>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{displayFiles.map((file, index) => {
|
||||
const fileName = file.split('/').pop() || file;
|
||||
const fileName = file.path.split('/').pop() || file.path;
|
||||
return (
|
||||
<Tooltip key={index} title={file} placement='top'>
|
||||
<FileChip>{fileName}</FileChip>
|
||||
<Tooltip key={index} title={`${file.path} (${file.status})`} placement='top'>
|
||||
<FileChip $status={file.status}>{fileName}</FileChip>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
|
|
@ -195,6 +198,19 @@ export default function GitHistory(): React.JSX.Element {
|
|||
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
||||
const [viewMode, setViewMode] = useState<'current' | 'all'>('current');
|
||||
const [shouldSelectFirst, setShouldSelectFirst] = useState(false);
|
||||
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' | 'info' }>({
|
||||
open: false,
|
||||
message: '',
|
||||
severity: 'info',
|
||||
});
|
||||
|
||||
const showSnackbar = (message: string, severity: 'success' | 'error' | 'info' = 'info') => {
|
||||
setSnackbar({ open: true, message, severity });
|
||||
};
|
||||
|
||||
const handleCloseSnackbar = () => {
|
||||
setSnackbar(previous => ({ ...previous, open: false }));
|
||||
};
|
||||
|
||||
// Create a tooltip wrapper that passes the translation function
|
||||
// The props coming from react-git-log don't include 't', so we add it
|
||||
|
|
@ -387,9 +403,21 @@ export default function GitHistory(): React.JSX.Element {
|
|||
// Trigger git log refresh after discard
|
||||
setShouldSelectFirst(true);
|
||||
}}
|
||||
showSnackbar={showSnackbar}
|
||||
/>
|
||||
</DiffPanelWrapper>
|
||||
</ContentWrapper>
|
||||
|
||||
<Snackbar
|
||||
open={snackbar.open}
|
||||
autoHideDuration={4000}
|
||||
onClose={handleCloseSnackbar}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert onClose={handleCloseSnackbar} severity={snackbar.severity} sx={{ width: '100%' }}>
|
||||
{snackbar.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Root>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { GitFileStatus, IFileWithStatus } from '../../services/git/interface';
|
||||
|
||||
/**
|
||||
* Represents the author or committer of a commit.
|
||||
*/
|
||||
|
|
@ -12,6 +14,9 @@ export interface CommitAuthor {
|
|||
name: string;
|
||||
}
|
||||
|
||||
// Re-export for convenience
|
||||
export type { GitFileStatus, IFileWithStatus };
|
||||
|
||||
/**
|
||||
* Represents a single entry in the git log.
|
||||
*/
|
||||
|
|
@ -45,7 +50,7 @@ export interface GitLogEntry {
|
|||
*/
|
||||
parents: string[];
|
||||
/**
|
||||
* Array of file paths changed in this commit.
|
||||
* Array of files with status changed in this commit.
|
||||
*/
|
||||
files?: string[];
|
||||
files?: IFileWithStatus[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue