Various fixes for desktop (#4976)

# Description of Changes
- ~Force classic logo~
- Refer to email instead of username in SaaS sign in flow
- Allow drag-and-drop files into desktop app
- Convert terminology & icons from upload/download to open/save in
desktop version
This commit is contained in:
James Brunton
2025-11-25 13:11:13 +00:00
committed by GitHub
parent 991e03970e
commit 2d8b0ff08c
19 changed files with 195 additions and 37 deletions
+18 -2
View File
@@ -130,6 +130,8 @@
"save": "Save", "save": "Save",
"saveToBrowser": "Save to Browser", "saveToBrowser": "Save to Browser",
"download": "Download", "download": "Download",
"downloadUnavailable": "Download unavailable for this item",
"saveUnavailable": "Save unavailable for this item",
"pin": "Pin File (keep active after tool run)", "pin": "Pin File (keep active after tool run)",
"unpin": "Unpin File (replace after tool run)", "unpin": "Unpin File (replace after tool run)",
"undoOperationTooltip": "Click to undo the last operation and restore the original files", "undoOperationTooltip": "Click to undo the last operation and restore the original files",
@@ -872,7 +874,8 @@
}, },
"landing": { "landing": {
"addFiles": "Add Files", "addFiles": "Add Files",
"uploadFromComputer": "Upload from computer" "uploadFromComputer": "Upload from computer",
"openFromComputer": "Open from computer"
}, },
"viewPdf": { "viewPdf": {
"tags": "view,read,annotate,text,image,highlight,edit", "tags": "view,read,annotate,text,image,highlight,edit",
@@ -3877,6 +3880,7 @@
"exportAll": "Export PDF", "exportAll": "Export PDF",
"downloadSelected": "Download Selected Files", "downloadSelected": "Download Selected Files",
"downloadAll": "Download All", "downloadAll": "Download All",
"saveAll": "Save All",
"toggleTheme": "Toggle Theme", "toggleTheme": "Toggle Theme",
"toggleBookmarks": "Toggle Bookmarks", "toggleBookmarks": "Toggle Bookmarks",
"language": "Language", "language": "Language",
@@ -4570,12 +4574,17 @@
"or": "or", "or": "or",
"dropFileHere": "Drop file here or click to upload", "dropFileHere": "Drop file here or click to upload",
"dropFilesHere": "Drop files here or click the upload button", "dropFilesHere": "Drop files here or click the upload button",
"dropFilesHereOpen": "Drop files here or click the open button",
"pdfFilesOnly": "PDF files only", "pdfFilesOnly": "PDF files only",
"supportedFileTypes": "Supported file types", "supportedFileTypes": "Supported file types",
"upload": "Upload", "upload": "Upload",
"uploadFile": "Upload File", "uploadFile": "Upload File",
"uploadFiles": "Upload Files", "uploadFiles": "Upload Files",
"open": "Open",
"openFile": "Open File",
"openFiles": "Open Files",
"noFilesInStorage": "No files available in storage. Upload some files first.", "noFilesInStorage": "No files available in storage. Upload some files first.",
"noFilesInStorageOpen": "No files available in storage. Open some files first.",
"selectFromStorage": "Select from Storage", "selectFromStorage": "Select from Storage",
"backToTools": "Back to Tools", "backToTools": "Back to Tools",
"addFiles": "Add Files", "addFiles": "Add Files",
@@ -4621,6 +4630,8 @@
"myFiles": "My Files", "myFiles": "My Files",
"noRecentFiles": "No recent files found", "noRecentFiles": "No recent files found",
"googleDriveNotAvailable": "Google Drive integration not available", "googleDriveNotAvailable": "Google Drive integration not available",
"downloadSelected": "Download Selected",
"saveSelected": "Save Selected",
"openFiles": "Open Files", "openFiles": "Open Files",
"openFile": "Open File", "openFile": "Open File",
"details": "File Details", "details": "File Details",
@@ -5769,12 +5780,17 @@
"label": "Username", "label": "Username",
"placeholder": "Enter your username" "placeholder": "Enter your username"
}, },
"email": {
"label": "Email",
"placeholder": "Enter your email"
},
"password": { "password": {
"label": "Password", "label": "Password",
"placeholder": "Enter your password" "placeholder": "Enter your password"
}, },
"error": { "error": {
"emptyUsername": "Please enter your username", "emptyUsername": "Please enter your username",
"emptyEmail": "Please enter your email",
"emptyPassword": "Please enter your password", "emptyPassword": "Please enter your password",
"oauthFailed": "OAuth login failed. Please try again." "oauthFailed": "OAuth login failed. Please try again."
}, },
@@ -6032,4 +6048,4 @@
"tags": "text,annotation,label", "tags": "text,annotation,label",
"applySignatures": "Apply Text" "applySignatures": "Apply Text"
} }
} }
+25 -5
View File
@@ -119,6 +119,28 @@ pub fn run() {
cleanup_backend(); cleanup_backend();
// Allow the window to close // 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")] #[cfg(target_os = "macos")]
RunEvent::Opened { urls } => { RunEvent::Opened { urls } => {
add_log(format!("📂 Tauri file opened event: {:?}", urls)); add_log(format!("📂 Tauri file opened event: {:?}", urls));
@@ -128,11 +150,9 @@ pub fn run() {
let url_str = url.as_str(); let url_str = url.as_str();
if url_str.starts_with("file://") { if url_str.starts_with("file://") {
let file_path = url_str.strip_prefix("file://").unwrap_or(url_str); let file_path = url_str.strip_prefix("file://").unwrap_or(url_str);
if file_path.ends_with(".pdf") { add_log(format!("📂 Processing opened file: {}", file_path));
add_log(format!("📂 Processing opened PDF: {}", file_path)); add_opened_file(file_path.to_string());
add_opened_file(file_path.to_string()); added_files = true;
added_files = true;
}
} }
} }
// Emit a generic notification that files were added (frontend will re-read storage) // Emit a generic notification that files were added (frontend will re-read storage)
@@ -6,6 +6,8 @@ import { useFilesModalContext } from '@app/contexts/FilesModalContext';
import LocalIcon from '@app/components/shared/LocalIcon'; import LocalIcon from '@app/components/shared/LocalIcon';
import { BASE_PATH } from '@app/constants/app'; import { BASE_PATH } from '@app/constants/app';
import styles from '@app/components/fileEditor/FileEditor.module.css'; import styles from '@app/components/fileEditor/FileEditor.module.css';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
interface AddFileCardProps { interface AddFileCardProps {
onFileSelect: (files: File[]) => void; onFileSelect: (files: File[]) => void;
@@ -23,6 +25,8 @@ const AddFileCard = ({
const { openFilesModal } = useFilesModalContext(); const { openFilesModal } = useFilesModalContext();
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const [isUploadHover, setIsUploadHover] = useState(false); const [isUploadHover, setIsUploadHover] = useState(false);
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const handleCardClick = () => { const handleCardClick = () => {
openFilesModal(); openFilesModal();
@@ -152,10 +156,10 @@ const AddFileCard = ({
onClick={handleNativeUploadClick} onClick={handleNativeUploadClick}
onMouseEnter={() => setIsUploadHover(true)} onMouseEnter={() => setIsUploadHover(true)}
> >
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} /> <LocalIcon icon={icons.uploadIconName} width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
{isUploadHover && ( {isUploadHover && (
<span style={{ marginLeft: '.5rem' }}> <span style={{ marginLeft: '.5rem' }}>
{t('landing.uploadFromComputer', 'Upload from computer')} {terminology.uploadFromComputer}
</span> </span>
)} )}
</Button> </Button>
@@ -166,7 +170,7 @@ const AddFileCard = ({
className="text-[var(--accent-interactive)]" className="text-[var(--accent-interactive)]"
style={{ fontSize: '.8rem', textAlign: 'center', marginTop: '0.5rem' }} style={{ fontSize: '.8rem', textAlign: 'center', marginTop: '0.5rem' }}
> >
{t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} {terminology.dropFilesHere}
</span> </span>
</div> </div>
</div> </div>
@@ -3,7 +3,8 @@ import { Text, ActionIcon, CheckboxIndicator, Tooltip, Modal, Button, Group, Sta
import { useIsMobile } from '@app/hooks/useIsMobile'; import { useIsMobile } from '@app/hooks/useIsMobile';
import { alert } from '@app/components/toast'; import { alert } from '@app/components/toast';
import { useTranslation } from 'react-i18next'; 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 CloseIcon from '@mui/icons-material/Close';
import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityIcon from '@mui/icons-material/Visibility';
import UnarchiveIcon from '@mui/icons-material/Unarchive'; import UnarchiveIcon from '@mui/icons-material/Unarchive';
@@ -57,6 +58,9 @@ const FileEditorThumbnail = ({
isSupported = true, isSupported = true,
}: FileEditorThumbnailProps) => { }: FileEditorThumbnailProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const DownloadOutlinedIcon = icons.download;
const { const {
pinFile, pinFile,
unpinFile, unpinFile,
@@ -204,7 +208,7 @@ const FileEditorThumbnail = ({
{ {
id: 'download', id: 'download',
icon: <DownloadOutlinedIcon style={{ fontSize: 20 }} />, icon: <DownloadOutlinedIcon style={{ fontSize: 20 }} />,
label: t('download', 'Download'), label: terminology.download,
onClick: (e) => { onClick: (e) => {
e.stopPropagation(); e.stopPropagation();
onDownloadFile(file.id); onDownloadFile(file.id);
@@ -5,12 +5,16 @@ import { useTranslation } from 'react-i18next';
import { useFileManagerContext } from '@app/contexts/FileManagerContext'; import { useFileManagerContext } from '@app/contexts/FileManagerContext';
import LocalIcon from '@app/components/shared/LocalIcon'; import LocalIcon from '@app/components/shared/LocalIcon';
import { BASE_PATH } from '@app/constants/app'; import { BASE_PATH } from '@app/constants/app';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
const EmptyFilesState: React.FC = () => { const EmptyFilesState: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const { onLocalFileClick } = useFileManagerContext(); const { onLocalFileClick } = useFileManagerContext();
const [isUploadHover, setIsUploadHover] = useState(false); const [isUploadHover, setIsUploadHover] = useState(false);
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const handleUploadClick = () => { const handleUploadClick = () => {
onLocalFileClick(); onLocalFileClick();
@@ -91,10 +95,10 @@ const EmptyFilesState: React.FC = () => {
onClick={handleUploadClick} onClick={handleUploadClick}
onMouseEnter={() => setIsUploadHover(true)} onMouseEnter={() => setIsUploadHover(true)}
> >
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} /> <LocalIcon icon={icons.uploadIconName} width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
{isUploadHover && ( {isUploadHover && (
<span style={{ marginLeft: '.5rem' }}> <span style={{ marginLeft: '.5rem' }}>
{t('landing.uploadFromComputer', 'Upload from computer')} {terminology.uploadFromComputer}
</span> </span>
)} )}
</Button> </Button>
@@ -105,7 +109,7 @@ const EmptyFilesState: React.FC = () => {
className="text-[var(--accent-interactive)]" className="text-[var(--accent-interactive)]"
style={{ fontSize: '.8rem', textAlign: 'center' }} style={{ fontSize: '.8rem', textAlign: 'center' }}
> >
{t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} {terminology.dropFilesHere}
</span> </span>
</div> </div>
</div> </div>
@@ -2,12 +2,16 @@ import React from "react";
import { Group, Text, ActionIcon, Tooltip } from "@mantine/core"; import { Group, Text, ActionIcon, Tooltip } from "@mantine/core";
import SelectAllIcon from "@mui/icons-material/SelectAll"; import SelectAllIcon from "@mui/icons-material/SelectAll";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import DownloadIcon from "@mui/icons-material/Download";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useFileManagerContext } from "@app/contexts/FileManagerContext"; import { useFileManagerContext } from "@app/contexts/FileManagerContext";
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
const FileActions: React.FC = () => { const FileActions: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const DownloadIcon = icons.download;
const { recentFiles, selectedFileIds, filteredFiles, onSelectAll, onDeleteSelected, onDownloadSelected } = const { recentFiles, selectedFileIds, filteredFiles, onSelectAll, onDeleteSelected, onDownloadSelected } =
useFileManagerContext(); useFileManagerContext();
@@ -95,7 +99,7 @@ const FileActions: React.FC = () => {
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip label={t("fileManager.downloadSelected", "Download Selected")}> <Tooltip label={terminology.downloadSelected}>
<ActionIcon <ActionIcon
variant="light" variant="light"
size="sm" size="sm"
@@ -1,11 +1,12 @@
import React from 'react'; import React from 'react';
import { Stack, Text, Button, Group } from '@mantine/core'; import { Stack, Text, Button, Group } from '@mantine/core';
import HistoryIcon from '@mui/icons-material/History'; import HistoryIcon from '@mui/icons-material/History';
import UploadIcon from '@mui/icons-material/Upload';
import CloudIcon from '@mui/icons-material/Cloud'; import CloudIcon from '@mui/icons-material/Cloud';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFileManagerContext } from '@app/contexts/FileManagerContext'; import { useFileManagerContext } from '@app/contexts/FileManagerContext';
import { useGoogleDrivePicker } from '@app/hooks/useGoogleDrivePicker'; import { useGoogleDrivePicker } from '@app/hooks/useGoogleDrivePicker';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
interface FileSourceButtonsProps { interface FileSourceButtonsProps {
horizontal?: boolean; horizontal?: boolean;
@@ -17,6 +18,9 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
const { activeSource, onSourceChange, onLocalFileClick, onGoogleDriveSelect } = useFileManagerContext(); const { activeSource, onSourceChange, onLocalFileClick, onGoogleDriveSelect } = useFileManagerContext();
const { t } = useTranslation(); const { t } = useTranslation();
const { isEnabled: isGoogleDriveEnabled, openPicker: openGoogleDrivePicker } = useGoogleDrivePicker(); const { isEnabled: isGoogleDriveEnabled, openPicker: openGoogleDrivePicker } = useGoogleDrivePicker();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const UploadIcon = icons.upload;
const handleGoogleDriveClick = async () => { const handleGoogleDriveClick = async () => {
try { try {
@@ -76,7 +80,7 @@ const FileSourceButtons: React.FC<FileSourceButtonsProps> = ({
} }
}} }}
> >
{horizontal ? t('fileUpload.uploadFiles', 'Upload') : t('fileUpload.uploadFiles', 'Upload Files')} {horizontal ? terminology.upload : terminology.uploadFiles}
</Button> </Button>
<Button <Button
@@ -2,7 +2,6 @@ import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'
import { ActionIcon, CheckboxIndicator } from '@mantine/core'; import { ActionIcon, CheckboxIndicator } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinIcon from '@mui/icons-material/PushPin';
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
@@ -13,6 +12,8 @@ import styles from '@app/components/pageEditor/PageEditor.module.css';
import { useFileContext } from '@app/contexts/FileContext'; import { useFileContext } from '@app/contexts/FileContext';
import { FileId } from '@app/types/file'; import { FileId } from '@app/types/file';
import { PrivateContent } from '@app/components/shared/PrivateContent'; import { PrivateContent } from '@app/components/shared/PrivateContent';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
interface FileItem { interface FileItem {
id: FileId; id: FileId;
@@ -51,6 +52,9 @@ const FileThumbnail = ({
isSupported = true, isSupported = true,
}: FileThumbnailProps) => { }: FileThumbnailProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const DownloadOutlinedIcon = icons.download;
const { pinFile, unpinFile, isFilePinned, activeFiles } = useFileContext(); const { pinFile, unpinFile, isFilePinned, activeFiles } = useFileContext();
// ---- Drag state ---- // ---- Drag state ----
@@ -86,7 +90,7 @@ const FileThumbnail = ({
} }
// If we can't find a way to download, surface a status message // If we can't find a way to download, surface a status message
onSetStatus?.(typeof t === 'function' ? t('downloadUnavailable', 'Download unavailable for this item') : 'Download unavailable for this item'); onSetStatus?.(terminology.downloadUnavailable);
}, [file, onDownloadFile, onSetStatus, t]); }, [file, onDownloadFile, onSetStatus, t]);
const handleRef = useRef<HTMLSpanElement | null>(null); const handleRef = useRef<HTMLSpanElement | null>(null);
@@ -279,7 +283,7 @@ const FileThumbnail = ({
onClick={() => { downloadSelectedFile(); setShowActions(false); }} onClick={() => { downloadSelectedFile(); setShowActions(false); }}
> >
<DownloadOutlinedIcon fontSize="small" /> <DownloadOutlinedIcon fontSize="small" />
<span>{t('download', 'Download')}</span> <span>{terminology.download}</span>
</button> </button>
<div className={styles.actionsDivider} /> <div className={styles.actionsDivider} />
@@ -16,6 +16,7 @@ import {
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FileId } from '@app/types/file'; import { FileId } from '@app/types/file';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
interface FilePickerModalProps { interface FilePickerModalProps {
opened: boolean; opened: boolean;
@@ -31,6 +32,7 @@ const FilePickerModal = ({
onSelectFiles, onSelectFiles,
}: FilePickerModalProps) => { }: FilePickerModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]); const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]);
// Reset selection when modal opens // Reset selection when modal opens
@@ -130,7 +132,7 @@ const FilePickerModal = ({
<Stack gap="md"> <Stack gap="md">
{storedFiles.length === 0 ? ( {storedFiles.length === 0 ? (
<Text c="dimmed" ta="center" py="xl"> <Text c="dimmed" ta="center" py="xl">
{t("fileUpload.noFilesInStorage", "No files available in storage. Upload some files first.")} {terminology.noFilesInStorage}
</Text> </Text>
) : ( ) : (
<> <>
@@ -253,7 +255,7 @@ const FilePickerModal = ({
disabled={selectedFileIds.length === 0} disabled={selectedFileIds.length === 0}
> >
{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") : t("fileUpload.loadFromStorage", "Load Files")
} }
</Button> </Button>
@@ -8,6 +8,8 @@ import { useFilesModalContext } from '@app/contexts/FilesModalContext';
import { BASE_PATH } from '@app/constants/app'; import { BASE_PATH } from '@app/constants/app';
import { useLogoPath } from '@app/hooks/useLogoPath'; import { useLogoPath } from '@app/hooks/useLogoPath';
import { useFileManager } from '@app/hooks/useFileManager'; import { useFileManager } from '@app/hooks/useFileManager';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
const LandingPage = () => { const LandingPage = () => {
const { addFiles } = useFileHandler(); const { addFiles } = useFileHandler();
@@ -19,6 +21,8 @@ const LandingPage = () => {
const logoPath = useLogoPath(); const logoPath = useLogoPath();
const { loadRecentFiles } = useFileManager(); const { loadRecentFiles } = useFileManager();
const [hasRecents, setHasRecents] = React.useState<boolean>(false); const [hasRecents, setHasRecents] = React.useState<boolean>(false);
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const handleFileDrop = async (files: File[]) => { const handleFileDrop = async (files: File[]) => {
await addFiles(files); await addFiles(files);
@@ -187,10 +191,10 @@ const LandingPage = () => {
onClick={handleNativeUploadClick} onClick={handleNativeUploadClick}
onMouseEnter={() => setIsUploadHover(true)} onMouseEnter={() => setIsUploadHover(true)}
> >
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} /> <LocalIcon icon={icons.uploadIconName} width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
{isUploadHover && ( {isUploadHover && (
<span style={{ marginLeft: '.5rem' }}> <span style={{ marginLeft: '.5rem' }}>
{t('landing.uploadFromComputer', 'Upload from computer')} {terminology.uploadFromComputer}
</span> </span>
)} )}
</Button> </Button>
@@ -239,7 +243,7 @@ const LandingPage = () => {
className="text-[var(--accent-interactive)]" className="text-[var(--accent-interactive)]"
style={{ fontSize: '.8rem' }} style={{ fontSize: '.8rem' }}
> >
{t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} {terminology.dropFilesHere}
</span> </span>
</div> </div>
</Dropzone> </Dropzone>
@@ -6,6 +6,8 @@ import { useRightRail } from '@app/contexts/RightRailContext';
import { useFileState, useFileSelection } from '@app/contexts/FileContext'; import { useFileState, useFileSelection } from '@app/contexts/FileContext';
import { useNavigationState } from '@app/contexts/NavigationContext'; import { useNavigationState } from '@app/contexts/NavigationContext';
import { useTranslation } from 'react-i18next'; 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 LanguageSelector from '@app/components/shared/LanguageSelector';
import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider'; import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider';
@@ -44,6 +46,8 @@ export default function RightRail() {
const { sidebarRefs } = useSidebarContext(); const { sidebarRefs } = useSidebarContext();
const { position: tooltipPosition, offset: tooltipOffset } = useRightRailTooltipSide(sidebarRefs); const { position: tooltipPosition, offset: tooltipOffset } = useRightRailTooltipSide(sidebarRefs);
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const viewerContext = React.useContext(ViewerContext); const viewerContext = React.useContext(ViewerContext);
const { toggleTheme, themeMode } = useRainbowThemeContext(); const { toggleTheme, themeMode } = useRainbowThemeContext();
const { buttons, actions, allButtonsDisabled } = useRightRail(); const { buttons, actions, allButtonsDisabled } = useRightRail();
@@ -165,9 +169,9 @@ export default function RightRail() {
return t('rightRail.exportAll', 'Export PDF'); return t('rightRail.exportAll', 'Export PDF');
} }
if (selectedCount > 0) { 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]); }, [currentView, selectedCount, t]);
return ( return (
@@ -232,7 +236,7 @@ export default function RightRail() {
(currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled) (currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled)
} }
> >
<LocalIcon icon="download" width="1.5rem" height="1.5rem" /> <LocalIcon icon={icons.downloadIconName} width="1.5rem" height="1.5rem" />
</ActionIcon>, </ActionIcon>,
downloadTooltip, downloadTooltip,
tooltipPosition, tooltipPosition,
@@ -14,6 +14,7 @@ import { BookmarkNode } from '@app/utils/editTableOfContents';
import ErrorNotification from '@app/components/tools/shared/ErrorNotification'; import ErrorNotification from '@app/components/tools/shared/ErrorNotification';
import ResultsPreview from '@app/components/tools/shared/ResultsPreview'; import ResultsPreview from '@app/components/tools/shared/ResultsPreview';
import BookmarkEditor from '@app/components/tools/editTableOfContents/BookmarkEditor'; import BookmarkEditor from '@app/components/tools/editTableOfContents/BookmarkEditor';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
export interface EditTableOfContentsWorkbenchViewData { export interface EditTableOfContentsWorkbenchViewData {
bookmarks: BookmarkNode[]; bookmarks: BookmarkNode[];
@@ -40,6 +41,7 @@ interface EditTableOfContentsWorkbenchViewProps {
const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => { const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
if (!data) { if (!data) {
return ( return (
@@ -180,7 +182,7 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench
download={downloadFilename ?? undefined} download={downloadFilename ?? undefined}
leftSection={<LocalIcon icon='download-rounded' />} leftSection={<LocalIcon icon='download-rounded' />}
> >
{t('download', 'Download')} {terminology.download}
</Button> </Button>
)} )}
<Button <Button
@@ -1,13 +1,14 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { Button, Stack } from "@mantine/core"; import { Button, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import DownloadIcon from "@mui/icons-material/Download";
import UndoIcon from "@mui/icons-material/Undo"; import UndoIcon from "@mui/icons-material/Undo";
import ErrorNotification from "@app/components/tools/shared/ErrorNotification"; import ErrorNotification from "@app/components/tools/shared/ErrorNotification";
import ResultsPreview from "@app/components/tools/shared/ResultsPreview"; import ResultsPreview from "@app/components/tools/shared/ResultsPreview";
import { SuggestedToolsSection } from "@app/components/tools/shared/SuggestedToolsSection"; import { SuggestedToolsSection } from "@app/components/tools/shared/SuggestedToolsSection";
import { ToolOperationHook } from "@app/hooks/tools/shared/useToolOperation"; import { ToolOperationHook } from "@app/hooks/tools/shared/useToolOperation";
import { Tooltip } from "@app/components/shared/Tooltip"; import { Tooltip } from "@app/components/shared/Tooltip";
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
export interface ReviewToolStepProps<TParams = unknown> { export interface ReviewToolStepProps<TParams = unknown> {
isVisible: boolean; isVisible: boolean;
@@ -29,6 +30,9 @@ function ReviewStepContent<TParams = unknown>({
onUndo?: () => void; onUndo?: () => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const DownloadIcon = icons.download;
const stepRef = useRef<HTMLDivElement>(null); const stepRef = useRef<HTMLDivElement>(null);
const handleUndo = async () => { const handleUndo = async () => {
@@ -96,7 +100,7 @@ function ReviewStepContent<TParams = unknown>({
fullWidth fullWidth
mb="md" mb="md"
> >
{t("download", "Download")} {terminology.download}
</Button> </Button>
)} )}
@@ -6,6 +6,7 @@ import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import DownloadRoundedIcon from "@mui/icons-material/DownloadRounded"; import DownloadRoundedIcon from "@mui/icons-material/DownloadRounded";
import "@app/components/tools/showJS/ShowJSView.css"; import "@app/components/tools/showJS/ShowJSView.css";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useFileActionTerminology } from "@app/hooks/useFileActionTerminology";
import { import {
tokenizeToLines, tokenizeToLines,
@@ -28,6 +29,7 @@ interface ShowJSViewProps {
const ShowJSView: React.FC<ShowJSViewProps> = ({ data }) => { const ShowJSView: React.FC<ShowJSViewProps> = ({ data }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const terminology = useFileActionTerminology();
const text = useMemo(() => { const text = useMemo(() => {
if (typeof data === "string") return data; if (typeof data === "string") return data;
return data?.scriptText ?? ""; return data?.scriptText ?? "";
@@ -173,7 +175,7 @@ const ShowJSView: React.FC<ShowJSViewProps> = ({ data }) => {
disabled={!downloadUrl} disabled={!downloadUrl}
leftSection={<DownloadRoundedIcon fontSize="small" />} leftSection={<DownloadRoundedIcon fontSize="small" />}
> >
{t("download", "Download")} {terminology.download}
</Button> </Button>
<Button <Button
size="xs" size="xs"
@@ -0,0 +1,15 @@
import UploadIcon from '@mui/icons-material/Upload';
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
/**
* File action icons for web builds
* Desktop builds override this with different icons
*/
export function useFileActionIcons() {
return {
upload: UploadIcon,
download: DownloadOutlinedIcon,
uploadIconName: 'upload' as const,
downloadIconName: 'download' as const,
};
}
@@ -0,0 +1,22 @@
import { useTranslation } from 'react-i18next';
/**
* File action terminology for web builds
* Desktop builds override this with different terminology
*/
export function useFileActionTerminology() {
const { t } = useTranslation();
return {
uploadFiles: t('fileUpload.uploadFiles', 'Upload Files'),
uploadFile: t('fileUpload.uploadFile', 'Upload File'),
upload: t('fileUpload.upload', 'Upload'),
dropFilesHere: t('fileUpload.dropFilesHere', 'Drop files here or click the upload button'),
uploadFromComputer: t('landing.uploadFromComputer', 'Upload from computer'),
download: t('download', 'Download'),
downloadAll: t('rightRail.downloadAll', 'Download All'),
downloadSelected: t('fileManager.downloadSelected', 'Download Selected'),
downloadUnavailable: t('downloadUnavailable', 'Download unavailable for this item'),
noFilesInStorage: t('fileUpload.noFilesInStorage', 'No files available in storage. Upload some files first.'),
};
}
@@ -25,7 +25,9 @@ export const LoginForm: React.FC<LoginFormProps> = ({ serverUrl, isSaaS = false,
// Validation // Validation
if (!username.trim()) { if (!username.trim()) {
setValidationError(t('setup.login.error.emptyUsername', 'Please enter your username')); setValidationError(isSaaS
? t('setup.login.error.emptyEmail', 'Please enter your email')
: t('setup.login.error.emptyUsername', 'Please enter your username'));
return; return;
} }
@@ -132,8 +134,12 @@ export const LoginForm: React.FC<LoginFormProps> = ({ serverUrl, isSaaS = false,
)} )}
<TextInput <TextInput
label={t('setup.login.username.label', 'Username')} label={isSaaS
placeholder={t('setup.login.username.placeholder', 'Enter your username')} ? t('setup.login.email.label', 'Email')
: t('setup.login.username.label', 'Username')}
placeholder={isSaaS
? t('setup.login.email.placeholder', 'Enter your email')
: t('setup.login.username.placeholder', 'Enter your username')}
value={username} value={username}
onChange={(e) => { onChange={(e) => {
setUsername(e.target.value); setUsername(e.target.value);
@@ -0,0 +1,15 @@
import FolderOpenOutlinedIcon from '@mui/icons-material/FolderOpenOutlined';
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
/**
* File action icons for desktop builds
* Overrides core implementation with desktop-appropriate icons
*/
export function useFileActionIcons() {
return {
upload: FolderOpenOutlinedIcon,
download: SaveOutlinedIcon,
uploadIconName: 'folder-rounded' as const,
downloadIconName: 'save-rounded' as const,
};
}
@@ -0,0 +1,22 @@
import { useTranslation } from 'react-i18next';
/**
* File action terminology for desktop builds
* Overrides core implementation with desktop-appropriate terminology
*/
export function useFileActionTerminology() {
const { t } = useTranslation();
return {
uploadFiles: t('fileUpload.openFiles', 'Open Files'),
uploadFile: t('fileUpload.openFile', 'Open File'),
upload: t('fileUpload.open', 'Open'),
dropFilesHere: t('fileUpload.dropFilesHereOpen', 'Drop files here or click the open button'),
uploadFromComputer: t('landing.openFromComputer', 'Open from computer'),
download: t('save', 'Save'),
downloadAll: t('rightRail.saveAll', 'Save All'),
downloadSelected: t('fileManager.saveSelected', 'Save Selected'),
downloadUnavailable: t('saveUnavailable', 'Save unavailable for this item'),
noFilesInStorage: t('fileUpload.noFilesInStorageOpen', 'No files available in storage. Open some files first.'),
};
}