file view container's context menus keyed (97 -> 80)
The folder right-click menu and the search-results menu: copy path, chat about this, paste, new file/folder, upload, clone, hidden-files toggle, create dashboard, download video, dictate, open folder, show location, and the drop-to-upload overlay. "Hide hidden files" / "Show hidden files" was a ternary inside JSX, which the scanner's own docs say it cannot see — found by reading the file rather than by the tool. Two keys, one per branch, since English and Portuguese verbs differ by more than a negation. files.menu.* is a new sub-namespace under the existing files.* group rather than a flat name, since a bare "download" would otherwise collide with nothing today but reads as ambiguous next to files.downloadFailed.
This commit is contained in:
+22
-18
@@ -29,12 +29,14 @@ import {
|
|||||||
import type { UseFileBrowserAppType } from '../useFileBrowserApp';
|
import type { UseFileBrowserAppType } from '../useFileBrowserApp';
|
||||||
import { NewEntryDialog } from './NewEntryDialog';
|
import { NewEntryDialog } from './NewEntryDialog';
|
||||||
import { FileGrid } from './FileGrid';
|
import { FileGrid } from './FileGrid';
|
||||||
|
import { useTranslation } from '../../../../locales';
|
||||||
|
|
||||||
type FileViewContainerProps = {
|
type FileViewContainerProps = {
|
||||||
fileBrowserManager: UseFileBrowserAppType;
|
fileBrowserManager: UseFileBrowserAppType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps) => {
|
export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
searchQuery,
|
searchQuery,
|
||||||
searching,
|
searching,
|
||||||
@@ -85,11 +87,11 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
<ContextMenuContent className="z-[600]">
|
<ContextMenuContent className="z-[600]">
|
||||||
<ContextMenuItem onClick={handleCopyCurrentPath} className="cursor-pointer">
|
<ContextMenuItem onClick={handleCopyCurrentPath} className="cursor-pointer">
|
||||||
<ClipboardCopy className="mr-2 h-4 w-4" />
|
<ClipboardCopy className="mr-2 h-4 w-4" />
|
||||||
Copy path
|
{t('files.menu.copyPath')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={handleChatHere} className="cursor-pointer">
|
<ContextMenuItem onClick={handleChatHere} className="cursor-pointer">
|
||||||
<MessageSquare className="mr-2 h-4 w-4" />
|
<MessageSquare className="mr-2 h-4 w-4" />
|
||||||
Chat about this
|
{t('files.menu.chatAboutThis')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
{/* Gated on the clipboard, as both toolbars already were. Ungated it fell through to the
|
{/* Gated on the clipboard, as both toolbars already were. Ungated it fell through to the
|
||||||
OS clipboard path, which on an insecure origin toasts an error and otherwise silently
|
OS clipboard path, which on an insecure origin toasts an error and otherwise silently
|
||||||
@@ -97,7 +99,7 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
{clipboard !== null && (
|
{clipboard !== null && (
|
||||||
<ContextMenuItem onClick={handlePaste} className="cursor-pointer">
|
<ContextMenuItem onClick={handlePaste} className="cursor-pointer">
|
||||||
<ClipboardPaste className="mr-2 h-4 w-4" />
|
<ClipboardPaste className="mr-2 h-4 w-4" />
|
||||||
Paste
|
{t('files.menu.paste')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
)}
|
)}
|
||||||
{/* Both open the same dialog. It validates as you type — against the names already on screen —
|
{/* Both open the same dialog. It validates as you type — against the names already on screen —
|
||||||
@@ -105,43 +107,43 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
rejected request. The server still refuses to clobber; this is the courtesy, that is the lock. */}
|
rejected request. The server still refuses to clobber; this is the courtesy, that is the lock. */}
|
||||||
<ContextMenuItem onClick={() => setNewEntry('file')} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setNewEntry('file')} className="cursor-pointer">
|
||||||
<FilePlus className="mr-2 h-4 w-4" />
|
<FilePlus className="mr-2 h-4 w-4" />
|
||||||
New file
|
{t('files.menu.newFile')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => setNewEntry('folder')} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setNewEntry('folder')} className="cursor-pointer">
|
||||||
<FolderPlus className="mr-2 h-4 w-4" />
|
<FolderPlus className="mr-2 h-4 w-4" />
|
||||||
New folder
|
{t('files.menu.newFolder')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => fileInputRef.current?.click()} className="cursor-pointer">
|
<ContextMenuItem onClick={() => fileInputRef.current?.click()} className="cursor-pointer">
|
||||||
<Upload className="mr-2 h-4 w-4" />
|
<Upload className="mr-2 h-4 w-4" />
|
||||||
Upload files
|
{t('files.menu.uploadFiles')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => folderInputRef.current?.click()} className="cursor-pointer">
|
<ContextMenuItem onClick={() => folderInputRef.current?.click()} className="cursor-pointer">
|
||||||
<FolderUp className="mr-2 h-4 w-4" />
|
<FolderUp className="mr-2 h-4 w-4" />
|
||||||
Upload folder
|
{t('files.menu.uploadFolder')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
{/* Was toolbar-only, and the toolbar hides it under `md:` — so on a phone there was no way
|
{/* Was toolbar-only, and the toolbar hides it under `md:` — so on a phone there was no way
|
||||||
to clone at all. */}
|
to clone at all. */}
|
||||||
<ContextMenuItem onClick={() => setNewEntry('clone')} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setNewEntry('clone')} className="cursor-pointer">
|
||||||
<GitBranch className="mr-2 h-4 w-4" />
|
<GitBranch className="mr-2 h-4 w-4" />
|
||||||
Clone repository
|
{t('files.menu.cloneRepository')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem onClick={() => setShowHidden(!showHidden)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setShowHidden(!showHidden)} className="cursor-pointer">
|
||||||
{showHidden ? <EyeOff className="mr-2 h-4 w-4" /> : <Eye className="mr-2 h-4 w-4" />}
|
{showHidden ? <EyeOff className="mr-2 h-4 w-4" /> : <Eye className="mr-2 h-4 w-4" />}
|
||||||
{showHidden ? 'Hide hidden files' : 'Show hidden files'}
|
{showHidden ? t('files.menu.hideHiddenFiles') : t('files.menu.showHiddenFiles')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem onClick={handleCreateDashboardHere} className="cursor-pointer">
|
<ContextMenuItem onClick={handleCreateDashboardHere} className="cursor-pointer">
|
||||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||||
Create Dashboard here
|
{t('files.menu.createDashboardHere')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => setShowVideoDownload(true)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setShowVideoDownload(true)} className="cursor-pointer">
|
||||||
<Download className="mr-2 h-4 w-4" />
|
<Download className="mr-2 h-4 w-4" />
|
||||||
Download video
|
{t('files.menu.downloadVideo')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => setShowDictate(true)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => setShowDictate(true)} className="cursor-pointer">
|
||||||
<Mic className="mr-2 h-4 w-4" />
|
<Mic className="mr-2 h-4 w-4" />
|
||||||
Dictate
|
{t('files.menu.dictate')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
);
|
);
|
||||||
@@ -158,7 +160,7 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-duck-teal/10 border-2 border-dashed border-duck-teal rounded-lg m-2 pointer-events-none">
|
<div className="absolute inset-0 z-50 flex items-center justify-center bg-duck-teal/10 border-2 border-dashed border-duck-teal rounded-lg m-2 pointer-events-none">
|
||||||
<div className="flex flex-col items-center gap-2 text-duck-teal">
|
<div className="flex flex-col items-center gap-2 text-duck-teal">
|
||||||
<Upload className="h-8 w-8" />
|
<Upload className="h-8 w-8" />
|
||||||
<span className="text-sm font-medium">Drop files to upload</span>
|
<span className="text-sm font-medium">{t('files.menu.dropToUpload')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -213,23 +215,23 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
{isDir && (
|
{isDir && (
|
||||||
<ContextMenuItem onClick={() => handleSearchResultClick(entry)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => handleSearchResultClick(entry)} className="cursor-pointer">
|
||||||
<FolderOpen className="mr-2 h-4 w-4" />
|
<FolderOpen className="mr-2 h-4 w-4" />
|
||||||
Open folder
|
{t('files.menu.openFolder')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
)}
|
)}
|
||||||
<ContextMenuItem onClick={() => handleShowLocation(entry)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => handleShowLocation(entry)} className="cursor-pointer">
|
||||||
<FolderSearch className="mr-2 h-4 w-4" />
|
<FolderSearch className="mr-2 h-4 w-4" />
|
||||||
Show location
|
{t('files.menu.showLocation')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
{absPath && (
|
{absPath && (
|
||||||
<ContextMenuItem onClick={() => handleCopyAbsPath(absPath)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => handleCopyAbsPath(absPath)} className="cursor-pointer">
|
||||||
<ClipboardCopy className="mr-2 h-4 w-4" />
|
<ClipboardCopy className="mr-2 h-4 w-4" />
|
||||||
Copy path
|
{t('files.menu.copyPath')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
)}
|
)}
|
||||||
{absPath && !isDir && (
|
{absPath && !isDir && (
|
||||||
<ContextMenuItem onClick={() => handleDownloadPath(absPath)} className="cursor-pointer">
|
<ContextMenuItem onClick={() => handleDownloadPath(absPath)} className="cursor-pointer">
|
||||||
<Download className="mr-2 h-4 w-4" />
|
<Download className="mr-2 h-4 w-4" />
|
||||||
Download
|
{t('files.menu.download')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
)}
|
)}
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
@@ -238,7 +240,9 @@ export const FileViewContainer = ({ fileBrowserManager }: FileViewContainerProps
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : searchResults ? (
|
) : searchResults ? (
|
||||||
<div className="flex items-center justify-center py-20 text-duck-dark/40 text-sm">No results found</div>
|
<div className="flex items-center justify-center py-20 text-duck-dark/40 text-sm">
|
||||||
|
{t('files.menu.noResults')}
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
|
|||||||
@@ -484,7 +484,27 @@
|
|||||||
"duplicated": "Duplicated “{{name}}”",
|
"duplicated": "Duplicated “{{name}}”",
|
||||||
"pastedInto": "Pasted into “{{name}}”",
|
"pastedInto": "Pasted into “{{name}}”",
|
||||||
"unpinned": "Unpinned “{{name}}”",
|
"unpinned": "Unpinned “{{name}}”",
|
||||||
"pinned": "Pinned “{{name}}”"
|
"pinned": "Pinned “{{name}}”",
|
||||||
|
"menu": {
|
||||||
|
"copyPath": "Copy path",
|
||||||
|
"chatAboutThis": "Chat about this",
|
||||||
|
"paste": "Paste",
|
||||||
|
"newFile": "New file",
|
||||||
|
"newFolder": "New folder",
|
||||||
|
"uploadFiles": "Upload files",
|
||||||
|
"uploadFolder": "Upload folder",
|
||||||
|
"cloneRepository": "Clone repository",
|
||||||
|
"hideHiddenFiles": "Hide hidden files",
|
||||||
|
"showHiddenFiles": "Show hidden files",
|
||||||
|
"createDashboardHere": "Create Dashboard here",
|
||||||
|
"downloadVideo": "Download video",
|
||||||
|
"dictate": "Dictate",
|
||||||
|
"dropToUpload": "Drop files to upload",
|
||||||
|
"openFolder": "Open folder",
|
||||||
|
"showLocation": "Show location",
|
||||||
|
"download": "Download",
|
||||||
|
"noResults": "No results found"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"taskRunner": {
|
"taskRunner": {
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
|
|||||||
@@ -484,7 +484,27 @@
|
|||||||
"duplicated": "“{{name}}” duplicado",
|
"duplicated": "“{{name}}” duplicado",
|
||||||
"pastedInto": "Colado em “{{name}}”",
|
"pastedInto": "Colado em “{{name}}”",
|
||||||
"unpinned": "“{{name}}” removido dos fixados",
|
"unpinned": "“{{name}}” removido dos fixados",
|
||||||
"pinned": "“{{name}}” fixado"
|
"pinned": "“{{name}}” fixado",
|
||||||
|
"menu": {
|
||||||
|
"copyPath": "Copiar caminho",
|
||||||
|
"chatAboutThis": "Conversar sobre isto",
|
||||||
|
"paste": "Colar",
|
||||||
|
"newFile": "Novo ficheiro",
|
||||||
|
"newFolder": "Nova pasta",
|
||||||
|
"uploadFiles": "Carregar ficheiros",
|
||||||
|
"uploadFolder": "Carregar pasta",
|
||||||
|
"cloneRepository": "Clonar repositório",
|
||||||
|
"hideHiddenFiles": "Ocultar ficheiros ocultos",
|
||||||
|
"showHiddenFiles": "Mostrar ficheiros ocultos",
|
||||||
|
"createDashboardHere": "Criar painel aqui",
|
||||||
|
"downloadVideo": "Descarregar vídeo",
|
||||||
|
"dictate": "Ditar",
|
||||||
|
"dropToUpload": "Largue ficheiros para carregar",
|
||||||
|
"openFolder": "Abrir pasta",
|
||||||
|
"showLocation": "Mostrar localização",
|
||||||
|
"download": "Descarregar",
|
||||||
|
"noResults": "Nenhum resultado encontrado"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"taskRunner": {
|
"taskRunner": {
|
||||||
"stop": "Parar",
|
"stop": "Parar",
|
||||||
|
|||||||
Reference in New Issue
Block a user