fix(files): hide desktop-only reveal action and label download in browser clients
Reveal-in-file-manager was always offered whenever the server exposed revealPath, including in a plain browser tab where there is no local file manager to reveal into. Gate it behind a new isBrowserClientRuntime check (web platform, no Electron shell) and relabel the save action to "Download" for that case, since it triggers a browser-style file download rather than an in-place save. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
166b89d8db
commit
f02969548a
@@ -43,6 +43,7 @@ import { opencodeClient } from '@/lib/opencode/client';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
|
||||
import { isBrowserClientRuntime } from '@/lib/desktop';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type FileNode = {
|
||||
@@ -190,6 +191,7 @@ interface FileRowProps {
|
||||
root: string;
|
||||
isExpanded: boolean;
|
||||
isActive: boolean;
|
||||
isBrowserClient: boolean;
|
||||
status?: FileStatus | null;
|
||||
badge?: { modified: number; added: number } | null;
|
||||
permissions: {
|
||||
@@ -211,6 +213,7 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
root,
|
||||
isExpanded,
|
||||
isActive,
|
||||
isBrowserClient,
|
||||
status,
|
||||
badge,
|
||||
permissions,
|
||||
@@ -223,6 +226,9 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
const { t } = useI18n();
|
||||
const isDir = node.type === 'directory';
|
||||
const { canRename, canCreateFile, canCreateFolder, canDelete, canReveal } = permissions;
|
||||
const canDownload = !isDir && Boolean(downloadFile);
|
||||
const canRevealPath = canReveal && !isBrowserClient;
|
||||
const hasMenuActions = canRename || canCreateFile || canCreateFolder || canDelete || canDownload || canRevealPath;
|
||||
|
||||
// Menu open state is local to each row so opening a menu in one row
|
||||
// never re-renders its siblings. Previously this state lived on the
|
||||
@@ -231,10 +237,10 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
const [rightClickOpen, setRightClickOpen] = React.useState(false);
|
||||
|
||||
const handleContextMenu = React.useCallback((event?: React.MouseEvent) => {
|
||||
if (!canRename && !canCreateFile && !canCreateFolder && !canDelete && !canReveal) return;
|
||||
if (!hasMenuActions) return;
|
||||
event?.preventDefault();
|
||||
setRightClickOpen(true);
|
||||
}, [canRename, canCreateFile, canCreateFolder, canDelete, canReveal]);
|
||||
}, [hasMenuActions]);
|
||||
|
||||
const handleInteraction = React.useCallback(() => {
|
||||
if (isDir) {
|
||||
@@ -283,10 +289,10 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
toast.error(t('sidebarFilesTree.toast.operationFailed'));
|
||||
});
|
||||
}}>
|
||||
<Icon name="download" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.save')}
|
||||
<Icon name="download" className="mr-2 h-4 w-4" /> {t(isBrowserClient ? 'sidebarFilesTree.menu.download' : 'sidebarFilesTree.menu.save')}
|
||||
</Item>
|
||||
)}
|
||||
{canReveal && (
|
||||
{canRevealPath && (
|
||||
<Item onClick={(e: React.MouseEvent) => { e.stopPropagation(); onRevealPath(node.path); }}>
|
||||
<Icon name="folder-received" className="mr-2 h-4 w-4" /> {t(getRevealLabelKey())}
|
||||
</Item>
|
||||
@@ -362,7 +368,7 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{(canRename || canCreateFile || canCreateFolder || canDelete || canReveal) && (
|
||||
{hasMenuActions && (
|
||||
<div className="absolute right-1 top-1/2 -translate-y-1/2 opacity-0 focus-within:opacity-100 group-hover:opacity-100">
|
||||
<DropdownMenu
|
||||
open={contextMenuOpen}
|
||||
@@ -406,6 +412,7 @@ const areFileRowPropsEqual = (prev: FileRowProps, next: FileRowProps): boolean =
|
||||
&& prev.root === next.root
|
||||
&& prev.isExpanded === next.isExpanded
|
||||
&& prev.isActive === next.isActive
|
||||
&& prev.isBrowserClient === next.isBrowserClient
|
||||
&& prev.status === next.status
|
||||
&& prev.badge === next.badge
|
||||
&& prev.permissions === next.permissions
|
||||
@@ -422,7 +429,8 @@ const MemoizedFileRow = React.memo(FileRow, areFileRowPropsEqual);
|
||||
|
||||
export const SidebarFilesTree: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const { files } = useRuntimeAPIs();
|
||||
const { files, runtime } = useRuntimeAPIs();
|
||||
const isBrowserClient = isBrowserClientRuntime(runtime.platform);
|
||||
const currentDirectory = useEffectiveDirectory() ?? '';
|
||||
const root = normalizePath(currentDirectory.trim());
|
||||
const showHidden = useDirectoryShowHidden();
|
||||
@@ -1045,6 +1053,7 @@ export const SidebarFilesTree: React.FC = () => {
|
||||
root={root}
|
||||
isExpanded={isExpanded}
|
||||
isActive={isActive}
|
||||
isBrowserClient={isBrowserClient}
|
||||
status={!isDir ? getFileStatus(node.path) : undefined}
|
||||
badge={isDir ? getFolderBadge(node.path) : undefined}
|
||||
permissions={fileRowPermissions}
|
||||
|
||||
@@ -70,7 +70,7 @@ import { Icon } from "@/components/icon/Icon";
|
||||
import { useMessageTTS } from '@/hooks/useMessageTTS';
|
||||
import { ensurePierreThemeRegistered } from '@/lib/shiki/appThemeRegistry';
|
||||
import { getDefaultTheme } from '@/lib/theme/themes';
|
||||
import { openDesktopFileInApp, openDesktopPath } from '@/lib/desktop';
|
||||
import { isBrowserClientRuntime, openDesktopFileInApp, openDesktopPath } from '@/lib/desktop';
|
||||
import { useOpenInAppsStore } from '@/stores/useOpenInAppsStore';
|
||||
import { eventMatchesShortcut, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -361,6 +361,7 @@ interface FileRowProps {
|
||||
isExpanded: boolean;
|
||||
isActive: boolean;
|
||||
isMobile: boolean;
|
||||
isBrowserClient: boolean;
|
||||
alwaysShowActions: boolean;
|
||||
status?: FileStatus | null;
|
||||
badge?: { modified: number; added: number } | null;
|
||||
@@ -388,6 +389,7 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
isExpanded,
|
||||
isActive,
|
||||
isMobile,
|
||||
isBrowserClient,
|
||||
alwaysShowActions,
|
||||
status,
|
||||
badge,
|
||||
@@ -405,14 +407,17 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
const { t } = useI18n();
|
||||
const isDir = node.type === 'directory';
|
||||
const { canRename, canCreateFile, canCreateFolder, canDelete, canReveal } = permissions;
|
||||
const canDownload = !isDir && Boolean(downloadFile);
|
||||
const canRevealPath = canReveal && !isBrowserClient;
|
||||
const hasMenuActions = canRename || canCreateFile || canCreateFolder || canDelete || canDownload || canRevealPath;
|
||||
|
||||
const handleContextMenu = React.useCallback((event?: React.MouseEvent) => {
|
||||
if (!canRename && !canCreateFile && !canCreateFolder && !canDelete && !canReveal) {
|
||||
if (!hasMenuActions) {
|
||||
return;
|
||||
}
|
||||
event?.preventDefault();
|
||||
setRightClickMenuPath(node.path);
|
||||
}, [canRename, canCreateFile, canCreateFolder, canDelete, canReveal, node.path, setRightClickMenuPath]);
|
||||
}, [hasMenuActions, node.path, setRightClickMenuPath]);
|
||||
|
||||
const handleInteraction = React.useCallback(() => {
|
||||
if (isDir) {
|
||||
@@ -474,10 +479,10 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
toast.error(t('sidebarFilesTree.toast.operationFailed'));
|
||||
});
|
||||
}}>
|
||||
<Icon name="download" className="mr-2 size-4" /> {t('sidebarFilesTree.menu.save')}
|
||||
<Icon name="download" className="mr-2 size-4" /> {t(isBrowserClient ? 'sidebarFilesTree.menu.download' : 'sidebarFilesTree.menu.save')}
|
||||
</Item>
|
||||
)}
|
||||
{canReveal && (
|
||||
{canRevealPath && (
|
||||
<Item onClick={(e: React.MouseEvent) => { e.stopPropagation(); onRevealPath(node.path); }}>
|
||||
<Icon name="folder-received" className="mr-2 size-4" /> {t(getRevealLabelKey())}
|
||||
</Item>
|
||||
@@ -546,7 +551,7 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{(canRename || canCreateFile || canCreateFolder || canDelete || canReveal) && (
|
||||
{hasMenuActions && (
|
||||
<div className={cn(
|
||||
"absolute right-1 top-1/2 -translate-y-1/2",
|
||||
alwaysShowActions ? "opacity-100" : "opacity-0 focus-within:opacity-100 group-hover:opacity-100"
|
||||
@@ -720,6 +725,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const { files, runtime } = useRuntimeAPIs();
|
||||
const { currentTheme, availableThemes, lightThemeId, darkThemeId } = useThemeSystem();
|
||||
const { isMobile, isTablet, screenWidth } = useDeviceInfo();
|
||||
const isBrowserClient = isBrowserClientRuntime(runtime.platform);
|
||||
const alwaysShowActions = isMobile || isTablet;
|
||||
const showHidden = useDirectoryShowHidden();
|
||||
const showGitignored = useFilesViewShowGitignored();
|
||||
@@ -2302,6 +2308,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
isExpanded={isExpanded}
|
||||
isActive={isActive}
|
||||
isMobile={isMobile}
|
||||
isBrowserClient={isBrowserClient}
|
||||
alwaysShowActions={alwaysShowActions}
|
||||
status={!isDir ? getFileStatus(node.path) : undefined}
|
||||
badge={isDir ? getFolderBadge(node.path) : undefined}
|
||||
|
||||
Reference in New Issue
Block a user