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}