FIX: Resolve keyboard shortcuts and enhance image preview with i18n support

- Fix keyboard shortcut conflicts in FileViewer.tsx (Ctrl+F, H, ?, Space, A)
- Add comprehensive i18n translations for keyboard shortcuts help panel
- Integrate react-photo-view for enhanced fullscreen image viewing
- Simplify image preview by removing complex toolbar and hover hints
- Add proper error handling and loading states for image display
- Update English and Chinese translation files with new keyboard shortcut terms

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZacharyZcR
2025-09-25 09:34:51 +08:00
parent 8e22e76166
commit cc9ae3fcd3
5 changed files with 137 additions and 31 deletions

View File

@@ -50,6 +50,8 @@ import { EditorView, keymap } from "@codemirror/view";
import { searchKeymap, search } from "@codemirror/search";
import { defaultKeymap, history, historyKeymap, toggleComment } from "@codemirror/commands";
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
import { PhotoProvider, PhotoView } from "react-photo-view";
import "react-photo-view/dist/react-photo-view.css";
interface FileItem {
name: string;
@@ -286,6 +288,8 @@ export function FileViewer({
const [forceShowAsText, setForceShowAsText] = useState(false);
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false);
const [editorFocused, setEditorFocused] = useState(false);
const [imageLoadError, setImageLoadError] = useState(false);
const [imageLoading, setImageLoading] = useState(true);
const fileTypeInfo = getFileType(file.name);
@@ -469,7 +473,7 @@ export function FileViewer({
{showKeyboardShortcuts && isEditable && (
<div className="flex-shrink-0 bg-muted/30 border-b border-border p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold">Keyboard Shortcuts</h3>
<h3 className="text-sm font-semibold">{t("fileManager.keyboardShortcuts")}</h3>
<Button
variant="ghost"
size="sm"
@@ -481,81 +485,81 @@ export function FileViewer({
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div className="space-y-2">
<h4 className="font-medium text-muted-foreground">Search & Replace</h4>
<h4 className="font-medium text-muted-foreground">{t("fileManager.searchAndReplace")}</h4>
<div className="space-y-1">
<div className="flex justify-between">
<span>Search</span>
<span>{t("fileManager.search")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+F</kbd>
</div>
<div className="flex justify-between">
<span>Replace</span>
<span>{t("fileManager.replace")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+H</kbd>
</div>
<div className="flex justify-between">
<span>Find Next</span>
<span>{t("fileManager.findNext")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">F3</kbd>
</div>
<div className="flex justify-between">
<span>Find Previous</span>
<span>{t("fileManager.findPrevious")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Shift+F3</kbd>
</div>
</div>
</div>
<div className="space-y-2">
<h4 className="font-medium text-muted-foreground">Editing</h4>
<h4 className="font-medium text-muted-foreground">{t("fileManager.editing")}</h4>
<div className="space-y-1">
<div className="flex justify-between">
<span>Save</span>
<span>{t("fileManager.save")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+S</kbd>
</div>
<div className="flex justify-between">
<span>Select All</span>
<span>{t("fileManager.selectAll")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+A</kbd>
</div>
<div className="flex justify-between">
<span>Undo</span>
<span>{t("fileManager.undo")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+Z</kbd>
</div>
<div className="flex justify-between">
<span>Redo</span>
<span>{t("fileManager.redo")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+Y</kbd>
</div>
</div>
</div>
<div className="space-y-2">
<h4 className="font-medium text-muted-foreground">Navigation</h4>
<h4 className="font-medium text-muted-foreground">{t("fileManager.navigation")}</h4>
<div className="space-y-1">
<div className="flex justify-between">
<span>Go to Line</span>
<span>{t("fileManager.goToLine")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+G</kbd>
</div>
<div className="flex justify-between">
<span>Move Line Up</span>
<span>{t("fileManager.moveLineUp")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Alt+</kbd>
</div>
<div className="flex justify-between">
<span>Move Line Down</span>
<span>{t("fileManager.moveLineDown")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Alt+</kbd>
</div>
</div>
</div>
<div className="space-y-2">
<h4 className="font-medium text-muted-foreground">Code</h4>
<h4 className="font-medium text-muted-foreground">{t("fileManager.code")}</h4>
<div className="space-y-1">
<div className="flex justify-between">
<span>Toggle Comment</span>
<span>{t("fileManager.toggleComment")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+/</kbd>
</div>
<div className="flex justify-between">
<span>Indent</span>
<span>{t("fileManager.indent")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Tab</kbd>
</div>
<div className="flex justify-between">
<span>Outdent</span>
<span>{t("fileManager.outdent")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Shift+Tab</kbd>
</div>
<div className="flex justify-between">
<span>Auto Complete</span>
<span>{t("fileManager.autoComplete")}</span>
<kbd className="px-2 py-1 bg-background rounded text-xs">Ctrl+Space</kbd>
</div>
</div>
@@ -628,18 +632,60 @@ export function FileViewer({
</div>
)}
{/* Image preview */}
{/* Image preview with react-photo-view */}
{fileTypeInfo.type === "image" && !showLargeFileWarning && (
<div className="p-6 flex items-center justify-center h-full">
<img
src={`data:image/*;base64,${content}`}
alt={file.name}
className="max-w-full max-h-full object-contain rounded-lg shadow-sm"
onError={(e) => {
(e.target as HTMLElement).style.display = "none";
// Show error message instead
}}
/>
<div className="p-6 flex items-center justify-center h-full relative">
{imageLoadError ? (
// Error state
<div className="text-center text-muted-foreground">
<AlertCircle className="w-16 h-16 mx-auto mb-4 text-muted-foreground/50" />
<h3 className="text-lg font-medium mb-2">
{t("fileManager.imageLoadError")}
</h3>
<p className="text-sm mb-4">
{file.name}
</p>
{onDownload && (
<Button
variant="outline"
onClick={onDownload}
className="flex items-center gap-2 mx-auto"
>
<Download className="w-4 h-4" />
{t("fileManager.download")}
</Button>
)}
</div>
) : (
<PhotoProvider maskOpacity={0.7}>
<PhotoView src={`data:image/*;base64,${content}`}>
<img
src={`data:image/*;base64,${content}`}
alt={file.name}
className="max-w-full max-h-full object-contain rounded-lg shadow-sm cursor-pointer hover:shadow-lg transition-shadow"
style={{ maxHeight: "calc(100vh - 200px)" }}
onLoad={() => {
setImageLoading(false);
setImageLoadError(false);
}}
onError={() => {
setImageLoading(false);
setImageLoadError(true);
}}
/>
</PhotoView>
</PhotoProvider>
)}
{/* Loading state */}
{imageLoading && !imageLoadError && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">Loading image...</p>
</div>
</div>
)}
</div>
)}