diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 821357cd4..bf287c732 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -130,6 +130,8 @@ "save": "Save", "saveToBrowser": "Save to Browser", "download": "Download", + "downloadUnavailable": "Download unavailable for this item", + "saveUnavailable": "Save unavailable for this item", "pin": "Pin File (keep active after tool run)", "unpin": "Unpin File (replace after tool run)", "undoOperationTooltip": "Click to undo the last operation and restore the original files", @@ -872,7 +874,8 @@ }, "landing": { "addFiles": "Add Files", - "uploadFromComputer": "Upload from computer" + "uploadFromComputer": "Upload from computer", + "openFromComputer": "Open from computer" }, "viewPdf": { "tags": "view,read,annotate,text,image,highlight,edit", @@ -3877,6 +3880,7 @@ "exportAll": "Export PDF", "downloadSelected": "Download Selected Files", "downloadAll": "Download All", + "saveAll": "Save All", "toggleTheme": "Toggle Theme", "toggleBookmarks": "Toggle Bookmarks", "language": "Language", @@ -4570,12 +4574,17 @@ "or": "or", "dropFileHere": "Drop file here or click to upload", "dropFilesHere": "Drop files here or click the upload button", + "dropFilesHereOpen": "Drop files here or click the open button", "pdfFilesOnly": "PDF files only", "supportedFileTypes": "Supported file types", "upload": "Upload", "uploadFile": "Upload File", "uploadFiles": "Upload Files", + "open": "Open", + "openFile": "Open File", + "openFiles": "Open Files", "noFilesInStorage": "No files available in storage. Upload some files first.", + "noFilesInStorageOpen": "No files available in storage. Open some files first.", "selectFromStorage": "Select from Storage", "backToTools": "Back to Tools", "addFiles": "Add Files", @@ -4621,6 +4630,8 @@ "myFiles": "My Files", "noRecentFiles": "No recent files found", "googleDriveNotAvailable": "Google Drive integration not available", + "downloadSelected": "Download Selected", + "saveSelected": "Save Selected", "openFiles": "Open Files", "openFile": "Open File", "details": "File Details", @@ -5769,12 +5780,17 @@ "label": "Username", "placeholder": "Enter your username" }, + "email": { + "label": "Email", + "placeholder": "Enter your email" + }, "password": { "label": "Password", "placeholder": "Enter your password" }, "error": { "emptyUsername": "Please enter your username", + "emptyEmail": "Please enter your email", "emptyPassword": "Please enter your password", "oauthFailed": "OAuth login failed. Please try again." }, @@ -6032,4 +6048,4 @@ "tags": "text,annotation,label", "applySignatures": "Apply Text" } -} \ No newline at end of file +} diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 838bdf1ec..34e49c7be 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -119,6 +119,28 @@ pub fn run() { cleanup_backend(); // Allow the window to close } + RunEvent::WindowEvent { event: WindowEvent::DragDrop(drag_drop_event), .. } => { + use tauri::DragDropEvent; + match drag_drop_event { + DragDropEvent::Drop { paths, .. } => { + add_log(format!("📂 Files dropped: {:?}", paths)); + let mut added_files = false; + + for path in paths { + if let Some(path_str) = path.to_str() { + add_log(format!("📂 Processing dropped file: {}", path_str)); + add_opened_file(path_str.to_string()); + added_files = true; + } + } + + if added_files { + let _ = app_handle.emit("files-changed", ()); + } + } + _ => {} + } + } #[cfg(target_os = "macos")] RunEvent::Opened { urls } => { add_log(format!("📂 Tauri file opened event: {:?}", urls)); @@ -128,11 +150,9 @@ pub fn run() { let url_str = url.as_str(); if url_str.starts_with("file://") { let file_path = url_str.strip_prefix("file://").unwrap_or(url_str); - if file_path.ends_with(".pdf") { - add_log(format!("📂 Processing opened PDF: {}", file_path)); - add_opened_file(file_path.to_string()); - added_files = true; - } + add_log(format!("📂 Processing opened file: {}", file_path)); + add_opened_file(file_path.to_string()); + added_files = true; } } // Emit a generic notification that files were added (frontend will re-read storage) diff --git a/frontend/src/core/components/fileEditor/AddFileCard.tsx b/frontend/src/core/components/fileEditor/AddFileCard.tsx index fe833c17f..4cc3741d3 100644 --- a/frontend/src/core/components/fileEditor/AddFileCard.tsx +++ b/frontend/src/core/components/fileEditor/AddFileCard.tsx @@ -6,6 +6,8 @@ import { useFilesModalContext } from '@app/contexts/FilesModalContext'; import LocalIcon from '@app/components/shared/LocalIcon'; import { BASE_PATH } from '@app/constants/app'; import styles from '@app/components/fileEditor/FileEditor.module.css'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; +import { useFileActionIcons } from '@app/hooks/useFileActionIcons'; interface AddFileCardProps { onFileSelect: (files: File[]) => void; @@ -23,6 +25,8 @@ const AddFileCard = ({ const { openFilesModal } = useFilesModalContext(); const { colorScheme } = useMantineColorScheme(); const [isUploadHover, setIsUploadHover] = useState(false); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); const handleCardClick = () => { openFilesModal(); @@ -152,10 +156,10 @@ const AddFileCard = ({ onClick={handleNativeUploadClick} onMouseEnter={() => setIsUploadHover(true)} > - + {isUploadHover && ( - {t('landing.uploadFromComputer', 'Upload from computer')} + {terminology.uploadFromComputer} )} @@ -166,7 +170,7 @@ const AddFileCard = ({ className="text-[var(--accent-interactive)]" style={{ fontSize: '.8rem', textAlign: 'center', marginTop: '0.5rem' }} > - {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + {terminology.dropFilesHere} diff --git a/frontend/src/core/components/fileEditor/FileEditorThumbnail.tsx b/frontend/src/core/components/fileEditor/FileEditorThumbnail.tsx index 7e734b42f..32ad10840 100644 --- a/frontend/src/core/components/fileEditor/FileEditorThumbnail.tsx +++ b/frontend/src/core/components/fileEditor/FileEditorThumbnail.tsx @@ -3,7 +3,8 @@ import { Text, ActionIcon, CheckboxIndicator, Tooltip, Modal, Button, Group, Sta import { useIsMobile } from '@app/hooks/useIsMobile'; import { alert } from '@app/components/toast'; import { useTranslation } from 'react-i18next'; -import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; +import { useFileActionIcons } from '@app/hooks/useFileActionIcons'; import CloseIcon from '@mui/icons-material/Close'; import VisibilityIcon from '@mui/icons-material/Visibility'; import UnarchiveIcon from '@mui/icons-material/Unarchive'; @@ -57,6 +58,9 @@ const FileEditorThumbnail = ({ isSupported = true, }: FileEditorThumbnailProps) => { const { t } = useTranslation(); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); + const DownloadOutlinedIcon = icons.download; const { pinFile, unpinFile, @@ -204,7 +208,7 @@ const FileEditorThumbnail = ({ { id: 'download', icon: , - label: t('download', 'Download'), + label: terminology.download, onClick: (e) => { e.stopPropagation(); onDownloadFile(file.id); diff --git a/frontend/src/core/components/fileManager/EmptyFilesState.tsx b/frontend/src/core/components/fileManager/EmptyFilesState.tsx index c9f72a7ad..ae46909d1 100644 --- a/frontend/src/core/components/fileManager/EmptyFilesState.tsx +++ b/frontend/src/core/components/fileManager/EmptyFilesState.tsx @@ -5,12 +5,16 @@ import { useTranslation } from 'react-i18next'; import { useFileManagerContext } from '@app/contexts/FileManagerContext'; import LocalIcon from '@app/components/shared/LocalIcon'; import { BASE_PATH } from '@app/constants/app'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; +import { useFileActionIcons } from '@app/hooks/useFileActionIcons'; const EmptyFilesState: React.FC = () => { const { t } = useTranslation(); const { colorScheme } = useMantineColorScheme(); const { onLocalFileClick } = useFileManagerContext(); const [isUploadHover, setIsUploadHover] = useState(false); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); const handleUploadClick = () => { onLocalFileClick(); @@ -91,10 +95,10 @@ const EmptyFilesState: React.FC = () => { onClick={handleUploadClick} onMouseEnter={() => setIsUploadHover(true)} > - + {isUploadHover && ( - {t('landing.uploadFromComputer', 'Upload from computer')} + {terminology.uploadFromComputer} )} @@ -105,7 +109,7 @@ const EmptyFilesState: React.FC = () => { className="text-[var(--accent-interactive)]" style={{ fontSize: '.8rem', textAlign: 'center' }} > - {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + {terminology.dropFilesHere} diff --git a/frontend/src/core/components/fileManager/FileActions.tsx b/frontend/src/core/components/fileManager/FileActions.tsx index d32ed0314..8f919e92c 100644 --- a/frontend/src/core/components/fileManager/FileActions.tsx +++ b/frontend/src/core/components/fileManager/FileActions.tsx @@ -2,12 +2,16 @@ import React from "react"; import { Group, Text, ActionIcon, Tooltip } from "@mantine/core"; import SelectAllIcon from "@mui/icons-material/SelectAll"; import DeleteIcon from "@mui/icons-material/Delete"; -import DownloadIcon from "@mui/icons-material/Download"; import { useTranslation } from "react-i18next"; import { useFileManagerContext } from "@app/contexts/FileManagerContext"; +import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology"; +import { useFileActionIcons } from "@app/hooks/useFileActionIcons"; const FileActions: React.FC = () => { const { t } = useTranslation(); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); + const DownloadIcon = icons.download; const { recentFiles, selectedFileIds, filteredFiles, onSelectAll, onDeleteSelected, onDownloadSelected } = useFileManagerContext(); @@ -95,7 +99,7 @@ const FileActions: React.FC = () => { - + = ({ const { activeSource, onSourceChange, onLocalFileClick, onGoogleDriveSelect } = useFileManagerContext(); const { t } = useTranslation(); const { isEnabled: isGoogleDriveEnabled, openPicker: openGoogleDrivePicker } = useGoogleDrivePicker(); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); + const UploadIcon = icons.upload; const handleGoogleDriveClick = async () => { try { @@ -76,7 +80,7 @@ const FileSourceButtons: React.FC = ({ } }} > - {horizontal ? t('fileUpload.uploadFiles', 'Upload') : t('fileUpload.uploadFiles', 'Upload Files')} + {horizontal ? terminology.upload : terminology.uploadFiles}
diff --git a/frontend/src/core/components/shared/FilePickerModal.tsx b/frontend/src/core/components/shared/FilePickerModal.tsx index 336070a74..3f5e78b34 100644 --- a/frontend/src/core/components/shared/FilePickerModal.tsx +++ b/frontend/src/core/components/shared/FilePickerModal.tsx @@ -16,6 +16,7 @@ import { import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import { useTranslation } from 'react-i18next'; import { FileId } from '@app/types/file'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; interface FilePickerModalProps { opened: boolean; @@ -31,6 +32,7 @@ const FilePickerModal = ({ onSelectFiles, }: FilePickerModalProps) => { const { t } = useTranslation(); + const terminology = useFileActionTerminology(); const [selectedFileIds, setSelectedFileIds] = useState([]); // Reset selection when modal opens @@ -130,7 +132,7 @@ const FilePickerModal = ({ {storedFiles.length === 0 ? ( - {t("fileUpload.noFilesInStorage", "No files available in storage. Upload some files first.")} + {terminology.noFilesInStorage} ) : ( <> @@ -253,7 +255,7 @@ const FilePickerModal = ({ disabled={selectedFileIds.length === 0} > {selectedFileIds.length > 0 - ? `${t("fileUpload.loadFromStorage", "Load")} ${selectedFileIds.length} ${t("fileUpload.uploadFiles", "Files")}` + ? `${t("fileUpload.loadFromStorage", "Load")} ${selectedFileIds.length} ${terminology.uploadFiles}` : t("fileUpload.loadFromStorage", "Load Files") } diff --git a/frontend/src/core/components/shared/LandingPage.tsx b/frontend/src/core/components/shared/LandingPage.tsx index 88cbe73b6..46b75b7c5 100644 --- a/frontend/src/core/components/shared/LandingPage.tsx +++ b/frontend/src/core/components/shared/LandingPage.tsx @@ -8,6 +8,8 @@ import { useFilesModalContext } from '@app/contexts/FilesModalContext'; import { BASE_PATH } from '@app/constants/app'; import { useLogoPath } from '@app/hooks/useLogoPath'; import { useFileManager } from '@app/hooks/useFileManager'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; +import { useFileActionIcons } from '@app/hooks/useFileActionIcons'; const LandingPage = () => { const { addFiles } = useFileHandler(); @@ -19,6 +21,8 @@ const LandingPage = () => { const logoPath = useLogoPath(); const { loadRecentFiles } = useFileManager(); const [hasRecents, setHasRecents] = React.useState(false); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); const handleFileDrop = async (files: File[]) => { await addFiles(files); @@ -187,10 +191,10 @@ const LandingPage = () => { onClick={handleNativeUploadClick} onMouseEnter={() => setIsUploadHover(true)} > - + {isUploadHover && ( - {t('landing.uploadFromComputer', 'Upload from computer')} + {terminology.uploadFromComputer} )} @@ -239,7 +243,7 @@ const LandingPage = () => { className="text-[var(--accent-interactive)]" style={{ fontSize: '.8rem' }} > - {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + {terminology.dropFilesHere}
diff --git a/frontend/src/core/components/shared/RightRail.tsx b/frontend/src/core/components/shared/RightRail.tsx index 3cca4edef..e1270c60a 100644 --- a/frontend/src/core/components/shared/RightRail.tsx +++ b/frontend/src/core/components/shared/RightRail.tsx @@ -6,6 +6,8 @@ import { useRightRail } from '@app/contexts/RightRailContext'; import { useFileState, useFileSelection } from '@app/contexts/FileContext'; import { useNavigationState } from '@app/contexts/NavigationContext'; import { useTranslation } from 'react-i18next'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; +import { useFileActionIcons } from '@app/hooks/useFileActionIcons'; import LanguageSelector from '@app/components/shared/LanguageSelector'; import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider'; @@ -44,6 +46,8 @@ export default function RightRail() { const { sidebarRefs } = useSidebarContext(); const { position: tooltipPosition, offset: tooltipOffset } = useRightRailTooltipSide(sidebarRefs); const { t } = useTranslation(); + const terminology = useFileActionTerminology(); + const icons = useFileActionIcons(); const viewerContext = React.useContext(ViewerContext); const { toggleTheme, themeMode } = useRainbowThemeContext(); const { buttons, actions, allButtonsDisabled } = useRightRail(); @@ -165,9 +169,9 @@ export default function RightRail() { return t('rightRail.exportAll', 'Export PDF'); } if (selectedCount > 0) { - return t('rightRail.downloadSelected', 'Download Selected Files'); + return terminology.downloadSelected; } - return t('rightRail.downloadAll', 'Download All'); + return terminology.downloadAll; }, [currentView, selectedCount, t]); return ( @@ -232,7 +236,7 @@ export default function RightRail() { (currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled) } > - +
, downloadTooltip, tooltipPosition, diff --git a/frontend/src/core/components/tools/editTableOfContents/EditTableOfContentsWorkbenchView.tsx b/frontend/src/core/components/tools/editTableOfContents/EditTableOfContentsWorkbenchView.tsx index 228a5c83b..b737921e6 100644 --- a/frontend/src/core/components/tools/editTableOfContents/EditTableOfContentsWorkbenchView.tsx +++ b/frontend/src/core/components/tools/editTableOfContents/EditTableOfContentsWorkbenchView.tsx @@ -14,6 +14,7 @@ import { BookmarkNode } from '@app/utils/editTableOfContents'; import ErrorNotification from '@app/components/tools/shared/ErrorNotification'; import ResultsPreview from '@app/components/tools/shared/ResultsPreview'; import BookmarkEditor from '@app/components/tools/editTableOfContents/BookmarkEditor'; +import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology'; export interface EditTableOfContentsWorkbenchViewData { bookmarks: BookmarkNode[]; @@ -40,6 +41,7 @@ interface EditTableOfContentsWorkbenchViewProps { const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => { const { t } = useTranslation(); + const terminology = useFileActionTerminology(); if (!data) { return ( @@ -180,7 +182,7 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench download={downloadFilename ?? undefined} leftSection={} > - {t('download', 'Download')} + {terminology.download} )} )} diff --git a/frontend/src/core/components/tools/showJS/ShowJSView.tsx b/frontend/src/core/components/tools/showJS/ShowJSView.tsx index 83dc50869..e44226582 100644 --- a/frontend/src/core/components/tools/showJS/ShowJSView.tsx +++ b/frontend/src/core/components/tools/showJS/ShowJSView.tsx @@ -6,6 +6,7 @@ import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import DownloadRoundedIcon from "@mui/icons-material/DownloadRounded"; import "@app/components/tools/showJS/ShowJSView.css"; import { useTranslation } from "react-i18next"; +import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology"; import { tokenizeToLines, @@ -28,6 +29,7 @@ interface ShowJSViewProps { const ShowJSView: React.FC = ({ data }) => { const { t } = useTranslation(); + const terminology = useFileActionTerminology(); const text = useMemo(() => { if (typeof data === "string") return data; return data?.scriptText ?? ""; @@ -173,7 +175,7 @@ const ShowJSView: React.FC = ({ data }) => { disabled={!downloadUrl} leftSection={} > - {t("download", "Download")} + {terminology.download}