File in context indication (#4990)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Reece Browne
2025-11-25 20:28:24 +00:00
committed by GitHub
parent 53d167eda5
commit daf749e6be
6 changed files with 65 additions and 6 deletions
@@ -4686,7 +4686,9 @@
"download": "Download", "download": "Download",
"delete": "Delete", "delete": "Delete",
"unsupported": "Unsupported", "unsupported": "Unsupported",
"active": "Active",
"addToUpload": "Add to Upload", "addToUpload": "Add to Upload",
"closeFile": "Close File",
"deleteAll": "Delete All", "deleteAll": "Delete All",
"loadingFiles": "Loading files...", "loadingFiles": "Loading files...",
"noFiles": "No files available", "noFiles": "No files available",
@@ -12,6 +12,7 @@ import { FileManagerProvider } from '@app/contexts/FileManagerContext';
import { Z_INDEX_FILE_MANAGER_MODAL } from '@app/styles/zIndex'; import { Z_INDEX_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
import { isGoogleDriveConfigured } from '@app/services/googleDrivePickerService'; import { isGoogleDriveConfigured } from '@app/services/googleDrivePickerService';
import { loadScript } from '@app/utils/scriptLoader'; import { loadScript } from '@app/utils/scriptLoader';
import { useAllFiles } from '@app/contexts/FileContext';
interface FileManagerProps { interface FileManagerProps {
selectedTool?: Tool | null; selectedTool?: Tool | null;
@@ -25,6 +26,9 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
const { loadRecentFiles, handleRemoveFile, loading } = useFileManager(); const { loadRecentFiles, handleRemoveFile, loading } = useFileManager();
// Get active file IDs from FileContext to show which files are already loaded
const { fileIds: activeFileIds } = useAllFiles();
// File management handlers // File management handlers
const isFileSupported = useCallback((fileName: string) => { const isFileSupported = useCallback((fileName: string) => {
if (!selectedTool?.supportedFormats) return true; if (!selectedTool?.supportedFormats) return true;
@@ -175,6 +179,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
modalHeight={modalHeight} modalHeight={modalHeight}
refreshRecentFiles={refreshRecentFiles} refreshRecentFiles={refreshRecentFiles}
isLoading={loading} isLoading={loading}
activeFileIds={activeFileIds}
> >
{isMobile ? <MobileLayout /> : <DesktopLayout />} {isMobile ? <MobileLayout /> : <DesktopLayout />}
</FileManagerProvider> </FileManagerProvider>
@@ -30,6 +30,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
onDownloadSingle, onDownloadSingle,
isFileSupported, isFileSupported,
isLoading, isLoading,
activeFileIds,
} = useFileManagerContext(); } = useFileManagerContext();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -55,6 +56,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
// All files in filteredFiles are now leaf files only // All files in filteredFiles are now leaf files only
const historyFiles = loadedHistoryFiles.get(file.id) || []; const historyFiles = loadedHistoryFiles.get(file.id) || [];
const isExpanded = expandedFileIds.has(file.id); const isExpanded = expandedFileIds.has(file.id);
const isActive = activeFileIds.includes(file.id);
return ( return (
<React.Fragment key={file.id}> <React.Fragment key={file.id}>
@@ -68,6 +70,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
onDoubleClick={() => onFileDoubleClick(file)} onDoubleClick={() => onFileDoubleClick(file)}
isHistoryFile={false} // All files here are leaf files isHistoryFile={false} // All files here are leaf files
isLatestVersion={true} // All files here are the latest versions isLatestVersion={true} // All files here are the latest versions
isActive={isActive}
/> />
<FileHistoryGroup <FileHistoryGroup
@@ -6,6 +6,7 @@ import DownloadIcon from '@mui/icons-material/Download';
import HistoryIcon from '@mui/icons-material/History'; import HistoryIcon from '@mui/icons-material/History';
import RestoreIcon from '@mui/icons-material/Restore'; import RestoreIcon from '@mui/icons-material/Restore';
import UnarchiveIcon from '@mui/icons-material/Unarchive'; import UnarchiveIcon from '@mui/icons-material/Unarchive';
import CloseIcon from '@mui/icons-material/Close';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { getFileSize, getFileDate } from '@app/utils/fileUtils'; import { getFileSize, getFileDate } from '@app/utils/fileUtils';
import { FileId, StirlingFileStub } from '@app/types/fileContext'; import { FileId, StirlingFileStub } from '@app/types/fileContext';
@@ -14,6 +15,7 @@ import { zipFileService } from '@app/services/zipFileService';
import ToolChain from '@app/components/shared/ToolChain'; import ToolChain from '@app/components/shared/ToolChain';
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex'; import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
import { PrivateContent } from '@app/components/shared/PrivateContent'; import { PrivateContent } from '@app/components/shared/PrivateContent';
import { useFileManagement } from '@app/contexts/FileContext';
interface FileListItemProps { interface FileListItemProps {
file: StirlingFileStub; file: StirlingFileStub;
@@ -26,6 +28,7 @@ interface FileListItemProps {
isLast?: boolean; isLast?: boolean;
isHistoryFile?: boolean; // Whether this is a history file (indented) isHistoryFile?: boolean; // Whether this is a history file (indented)
isLatestVersion?: boolean; // Whether this is the latest version (shows chevron) isLatestVersion?: boolean; // Whether this is the latest version (shows chevron)
isActive?: boolean; // Whether this file is currently loaded in FileContext
} }
const FileListItem: React.FC<FileListItemProps> = ({ const FileListItem: React.FC<FileListItemProps> = ({
@@ -37,12 +40,14 @@ const FileListItem: React.FC<FileListItemProps> = ({
onDownload, onDownload,
onDoubleClick, onDoubleClick,
isHistoryFile = false, isHistoryFile = false,
isLatestVersion = false isLatestVersion = false,
isActive = false
}) => { }) => {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const {expandedFileIds, onToggleExpansion, onUnzipFile } = useFileManagerContext(); const {expandedFileIds, onToggleExpansion, onUnzipFile } = useFileManagerContext();
const { removeFiles } = useFileManagement();
// Check if this is a ZIP file // Check if this is a ZIP file
const isZipFile = zipFileService.isZipFileStub(file); const isZipFile = zipFileService.isZipFileStub(file);
@@ -61,8 +66,10 @@ const FileListItem: React.FC<FileListItemProps> = ({
<Box <Box
p="sm" p="sm"
style={{ style={{
cursor: isHistoryFile ? 'default' : 'pointer', cursor: isHistoryFile || isActive ? 'default' : 'pointer',
backgroundColor: isSelected backgroundColor: isActive
? 'var(--file-active-bg)'
: isSelected
? 'var(--mantine-color-gray-1)' ? 'var(--mantine-color-gray-1)'
: (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'), : (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
opacity: isSupported ? 1 : 0.5, opacity: isSupported ? 1 : 0.5,
@@ -74,7 +81,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history
}} }}
onClick={isHistoryFile ? undefined : (e) => onSelect(e.shiftKey)} onClick={isHistoryFile || isActive ? undefined : (e) => onSelect(e.shiftKey)}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
@@ -84,14 +91,16 @@ const FileListItem: React.FC<FileListItemProps> = ({
<Box> <Box>
{/* Checkbox for regular files only */} {/* Checkbox for regular files only */}
<Checkbox <Checkbox
checked={isSelected} checked={isActive || isSelected}
onChange={() => {}} // Handled by parent onClick onChange={() => {}} // Handled by parent onClick
size="sm" size="sm"
pl="sm" pl="sm"
pr="xs" pr="xs"
disabled={isActive}
color={isActive ? "green" : undefined}
styles={{ styles={{
input: { input: {
cursor: 'pointer' cursor: isActive ? 'not-allowed' : 'pointer'
} }
}} }}
/> />
@@ -103,6 +112,19 @@ const FileListItem: React.FC<FileListItemProps> = ({
<Text size="sm" fw={500} truncate style={{ flex: 1 }}> <Text size="sm" fw={500} truncate style={{ flex: 1 }}>
<PrivateContent>{file.name}</PrivateContent> <PrivateContent>{file.name}</PrivateContent>
</Text> </Text>
{isActive && (
<Badge
size="xs"
variant="light"
style={{
backgroundColor: 'var(--file-active-badge-bg)',
color: 'var(--file-active-badge-fg)',
border: '1px solid var(--file-active-badge-border)'
}}
>
{t('fileManager.active', 'Active')}
</Badge>
)}
<Badge size="xs" variant="light" color={"blue"}> <Badge size="xs" variant="light" color={"blue"}>
v{currentVersion} v{currentVersion}
</Badge> </Badge>
@@ -151,6 +173,22 @@ const FileListItem: React.FC<FileListItemProps> = ({
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{/* Close file option for active files */}
{isActive && (
<>
<Menu.Item
leftSection={<CloseIcon style={{ fontSize: 16 }} />}
onClick={(e) => {
e.stopPropagation();
removeFiles([file.id]);
}}
>
{t('fileManager.closeFile', 'Close File')}
</Menu.Item>
<Menu.Divider />
</>
)}
{onDownload && ( {onDownload && (
<Menu.Item <Menu.Item
leftSection={<DownloadIcon style={{ fontSize: 16 }} />} leftSection={<DownloadIcon style={{ fontSize: 16 }} />}
@@ -20,6 +20,7 @@ interface FileManagerContextValue {
fileGroups: Map<FileId, StirlingFileStub[]>; fileGroups: Map<FileId, StirlingFileStub[]>;
loadedHistoryFiles: Map<FileId, StirlingFileStub[]>; loadedHistoryFiles: Map<FileId, StirlingFileStub[]>;
isLoading: boolean; isLoading: boolean;
activeFileIds: FileId[];
// Handlers // Handlers
onSourceChange: (source: 'recent' | 'local' | 'drive') => void; onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
@@ -63,6 +64,7 @@ interface FileManagerProviderProps {
modalHeight: string; modalHeight: string;
refreshRecentFiles: () => Promise<void>; refreshRecentFiles: () => Promise<void>;
isLoading: boolean; isLoading: boolean;
activeFileIds: FileId[];
} }
export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
@@ -77,6 +79,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
modalHeight, modalHeight,
refreshRecentFiles, refreshRecentFiles,
isLoading, isLoading,
activeFileIds,
}) => { }) => {
const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent'); const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent');
const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]); const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]);
@@ -618,6 +621,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
fileGroups, fileGroups,
loadedHistoryFiles, loadedHistoryFiles,
isLoading, isLoading,
activeFileIds,
// Handlers // Handlers
onSourceChange: handleSourceChange, onSourceChange: handleSourceChange,
@@ -654,6 +658,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
fileGroups, fileGroups,
loadedHistoryFiles, loadedHistoryFiles,
isLoading, isLoading,
activeFileIds,
handleSourceChange, handleSourceChange,
handleLocalFileClick, handleLocalFileClick,
handleFileSelect, handleFileSelect,
+6
View File
@@ -12,6 +12,12 @@
/* Inline highlights in summary */ /* Inline highlights in summary */
--spdf-compare-inline-removed-bg: rgba(255, 59, 48, 0.25); --spdf-compare-inline-removed-bg: rgba(255, 59, 48, 0.25);
--spdf-compare-inline-added-bg: rgba(52, 199, 89, 0.25); --spdf-compare-inline-added-bg: rgba(52, 199, 89, 0.25);
/* File Manager active file colors */
--file-active-bg: rgba(59, 130, 246, 0.1); /* Transparent blue for active file background */
--file-active-badge-bg: rgba(34, 197, 94, 0.15); /* Transparent green for active badge */
--file-active-badge-fg: #15803d; /* Green text for active badge */
--file-active-badge-border: rgba(34, 197, 94, 0.3); /* Green border for active badge */
} }
/* CSS variables for Tailwind + Mantine integration */ /* CSS variables for Tailwind + Mantine integration */