Localization actions

This commit is contained in:
Elio Struyf
2024-03-13 11:32:42 +01:00
parent fdcfdc971d
commit ec9f55b982
7 changed files with 270 additions and 128 deletions
@@ -11,6 +11,7 @@ import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
import { FilesProvider } from '../../providers/FilesProvider';
export interface IContentsProps {
pages: Page[];
@@ -32,18 +33,20 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
}, []);
return (
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
<div className="w-full flex-grow max-w-full mx-auto pb-6">
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
</div>
<FilesProvider files={pageItems}>
<PageLayout folders={pageFolders} totalPages={pageItems.length}>
<div className="w-full flex-grow max-w-full mx-auto pb-6">
{loading ? <Spinner type={loading} /> : <Overview pages={pageItems} settings={settings} />}
</div>
<SponsorMsg
beta={settings?.beta}
version={settings?.versionInfo}
isBacker={settings?.isBacker}
/>
<SponsorMsg
beta={settings?.beta}
version={settings?.versionInfo}
isBacker={settings?.isBacker}
/>
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
</PageLayout>
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=content" alt="Content metrics" />
</PageLayout>
</FilesProvider>
);
};
@@ -1,6 +1,6 @@
import * as React from 'react';
import { NavigationType } from '../../models';
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon } from '@heroicons/react/24/outline';
import { NavigationType, Page } from '../../models';
import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon, EyeIcon, LanguageIcon } from '@heroicons/react/24/outline';
import { useRecoilState, useRecoilValue } from 'recoil';
import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
import { ActionsBarItem } from './ActionsBarItem';
@@ -10,7 +10,9 @@ import { Alert } from '../Modals/Alert';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { CustomScript, ScriptType } from '../../../models';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown';
import { useFilesContext } from '../../providers/FilesProvider';
import { COMMAND_NAME, GeneralCommands } from '../../../constants';
export interface IActionsBarProps {
view: NavigationType;
@@ -24,10 +26,15 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
const [showAlert, setShowAlert] = React.useState(false);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const settings = useRecoilValue(SettingsSelector);
const { files } = useFilesContext();
const viewFile = React.useCallback(() => {
if (selectedFiles.length === 1 && view === NavigationType.Contents) {
messageHandler.send(DashboardMessage.openFile, selectedFiles[0]);
if (selectedFiles.length === 1) {
if (view === NavigationType.Contents) {
messageHandler.send(DashboardMessage.openFile, selectedFiles[0]);
} else if (view === NavigationType.Media) {
setSelectedItemAction({ path: selectedFiles[0], action: 'view' })
}
}
}, [selectedFiles]);
@@ -57,6 +64,75 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
}
}, [selectedFiles]);
const languageActions = React.useMemo(() => {
const actions: React.ReactNode[] = [];
if (view === NavigationType.Contents && files.length > 0 && selectedFiles.length === 1) {
const selectedItem = selectedFiles[0];
const page = ((files || []) as Page[]).find((f: Page) => f.fmFilePath === selectedItem);
if (page?.fmLocale) {
const locale = page.fmLocale;
const translations = page.fmTranslations;
actions.push(
<ActionsBarItem
key="translate"
onClick={() => {
messageHandler.send(GeneralCommands.toVSCode.runCommand, {
command: COMMAND_NAME.i18n.create,
args: selectedItem
})
}}>
<LanguageIcon className={`mr-2 h-4 w-4`} aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonTranslate)}</span>
</ActionsBarItem>
)
if (translations && Object.keys(translations).length > 0) {
const crntLocale = translations[locale.locale];
const otherLocales = Object.entries(translations).filter(([key]) => key !== locale.locale);
if (otherLocales.length > 0) {
actions.push(
<DropdownMenu>
<DropdownMenuTrigger
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'
>
<LanguageIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>{l10n.t(LocalizationKey.commonLanguages)}</span>
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
</DropdownMenuTrigger>
<DropdownMenuContent align='start'>
<DropdownMenuItem onClick={() => messageHandler.send(DashboardMessage.openFile, crntLocale.path)}>
<span>{crntLocale.locale.title || crntLocale.locale.locale}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{
otherLocales.map(([key, value]) => (
<DropdownMenuItem
key={key}
onClick={() => messageHandler.send(DashboardMessage.openFile, value.path)}
>
<span>{value.locale.title || value.locale.locale}</span>
</DropdownMenuItem>
))
}
</DropdownMenuContent>
</DropdownMenu>
)
}
}
}
}
return actions;
}, [files, selectedFiles]);
const customScriptActions = React.useMemo(() => {
if (!settings?.scripts) {
return null;
@@ -80,7 +156,7 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
disabled={selectedFiles.length === 0}
>
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
<span>Scripts</span>
<span>{l10n.t(LocalizationKey.commonScripts)}</span>
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
</DropdownMenuTrigger>
@@ -111,19 +187,13 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
aria-label="Item actions"
>
<div className='flex items-center space-x-6'>
{
view === NavigationType.Contents && (
<>
<ActionsBarItem
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
onClick={viewFile}
>
<EyeIcon className="w-4 h-4 mr-2" aria-hidden="true" />
<span>{l10n.t(LocalizationKey.commonView)}</span>
</ActionsBarItem>
</>
)
}
<ActionsBarItem
disabled={selectedFiles.length === 0 || selectedFiles.length > 1}
onClick={viewFile}
>
<EyeIcon className="w-4 h-4 mr-2" aria-hidden="true" />
<span>{l10n.t(LocalizationKey.commonView)}</span>
</ActionsBarItem>
{
view === NavigationType.Media && (
@@ -142,6 +212,8 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
)
}
{languageActions}
{customScriptActions}
<ActionsBarItem
@@ -170,8 +242,8 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
{showAlert && (
<Alert
title={`${l10n.t(LocalizationKey.commonDelete)}`}
description={`Are you sure you want to delete the selected files?`}
title={`${l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteTitle)}`}
description={l10n.t(LocalizationKey.dashboardHeaderActionsBarAlertDeleteDescription)}
okBtnText={l10n.t(LocalizationKey.commonDelete)}
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
dismiss={() => setShowAlert(false)}
@@ -313,6 +313,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
</button>
</div>
)}
<ItemSelection filePath={media.fsPath} />
</div>
)}
</button>
+99 -96
View File
@@ -28,6 +28,7 @@ import { MediaInfo } from '../../../models';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { MediaItemPanel } from './MediaItemPanel';
import { FilesProvider } from '../../providers/FilesProvider';
export interface IMediaProps { }
@@ -163,113 +164,115 @@ export const Media: React.FunctionComponent<IMediaProps> = (
});
return (
<PageLayout>
<div className="w-full h-full pb-6" {...getRootProps()}>
{viewData?.data?.filePath && (
<div className={`text-lg text-center mb-6`}>
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
<p className={`opacity-80 text-base`}>
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
</p>
</div>
)}
{isDragActive && (
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
<ArrowUpTrayIcon className={`h-32`} />
<p className={`text-xl max-w-md text-center`}>
{selectedFolder
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
</p>
</div>
)}
{allMedia.length === 0 && folders.length === 0 && !loading && (
<div className={`flex items-center justify-center h-full`}>
<div className={`max-w-xl text-center`}>
<FrontMatterIcon
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
/>
<p className={`text-xl font-medium`}>
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
<FilesProvider files={allMedia}>
<PageLayout>
<div className="w-full h-full pb-6" {...getRootProps()}>
{viewData?.data?.filePath && (
<div className={`text-lg text-center mb-6`}>
<p>{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}</p>
<p className={`opacity-80 text-base`}>
{l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}
</p>
</div>
</div>
)}
{contentFolders &&
contentFolders.length > 0 &&
contentFolders.map(
(group, idx) =>
group.folders &&
group.folders.length > 0 && (
<div key={`group-${idx}`} className={`mb-8`}>
<h2 className="text-lg mb-8 first-letter:uppercase">
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
</h2>
<List gap={0}>
{group.folders.map((folder) => (
<FolderItem
key={folder}
folder={folder}
staticFolder={currentStaticFolder}
wsFolder={settings?.wsFolder}
/>
))}
</List>
</div>
)
)}
{publicFolders && publicFolders.length > 0 && (
<div className={`mb-8`}>
{contentFolders && contentFolders.length > 0 && (
<h2 className="text-lg mb-8">
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
{currentStaticFolder && (
<span>
: <b>{currentStaticFolder}</b>
</span>
)}
</h2>
{isDragActive && (
<div className={`absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center z-50 text-[var(--vscode-foreground)] bg-[var(--vscode-editor-background)] opacity-75`}>
<ArrowUpTrayIcon className={`h-32`} />
<p className={`text-xl max-w-md text-center`}>
{selectedFolder
? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder)
: l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')}
</p>
</div>
)}
{allMedia.length === 0 && folders.length === 0 && !loading && (
<div className={`flex items-center justify-center h-full`}>
<div className={`max-w-xl text-center`}>
<FrontMatterIcon
className={`h-32 mx-auto opacity-90 mb-8 text-[var(--vscode-editor-foreground)]`}
/>
<p className={`text-xl font-medium`}>
{l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)}
</p>
</div>
</div>
)}
{contentFolders &&
contentFolders.length > 0 &&
contentFolders.map(
(group, idx) =>
group.folders &&
group.folders.length > 0 && (
<div key={`group-${idx}`} className={`mb-8`}>
<h2 className="text-lg mb-8 first-letter:uppercase">
{l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: <b>{group.title}</b>
</h2>
<List gap={0}>
{group.folders.map((folder) => (
<FolderItem
key={folder}
folder={folder}
staticFolder={currentStaticFolder}
wsFolder={settings?.wsFolder}
/>
))}
</List>
</div>
)
)}
<List gap={0}>
{publicFolders.map((folder) => (
<FolderItem
key={folder}
folder={folder}
staticFolder={currentStaticFolder}
wsFolder={settings?.wsFolder}
/>
))}
</List>
</div>
)}
{publicFolders && publicFolders.length > 0 && (
<div className={`mb-8`}>
{contentFolders && contentFolders.length > 0 && (
<h2 className="text-lg mb-8">
{l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)}
{currentStaticFolder && (
<span>
: <b>{currentStaticFolder}</b>
</span>
)}
</h2>
)}
<List>
{allMedia.map((file, idx) => (
<Item key={file.fsPath} media={file} />
))}
</List>
</div>
<List gap={0}>
{publicFolders.map((folder) => (
<FolderItem
key={folder}
folder={folder}
staticFolder={currentStaticFolder}
wsFolder={settings?.wsFolder}
/>
))}
</List>
</div>
)}
<MediaItemPanel allMedia={allMedia} />
<List>
{allMedia.map((file, idx) => (
<Item key={file.fsPath} media={file} />
))}
</List>
</div>
{loading && <Spinner />}
<MediaItemPanel allMedia={allMedia} />
<Lightbox />
{loading && <Spinner />}
<SponsorMsg
beta={settings?.beta}
version={settings?.versionInfo}
isBacker={settings?.isBacker}
/>
<Lightbox />
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
</PageLayout>
<SponsorMsg
beta={settings?.beta}
version={settings?.versionInfo}
isBacker={settings?.isBacker}
/>
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=media" alt="Media metrics" />
</PageLayout>
</FilesProvider>
);
};
@@ -0,0 +1,36 @@
import * as React from 'react';
import { Page } from '../models';
import { MediaInfo } from '../../models';
interface IFilesProviderProps {
files: Page[] | MediaInfo[];
}
const FilesContext = React.createContext<IFilesProviderProps | undefined>(undefined);
const FilesProvider: React.FunctionComponent<IFilesProviderProps> = ({ files, children }: React.PropsWithChildren<IFilesProviderProps>) => {
return (
<FilesContext.Provider
value={{
files
}}
>
{children}
</FilesContext.Provider>
)
};
const useFilesContext = (): IFilesProviderProps => {
const loadFunc = React.useContext(FilesContext);
if (loadFunc === undefined) {
throw new Error('useFilesContext must be used within the FilesProvider');
}
return loadFunc;
};
FilesContext.displayName = 'FilesContext';
FilesProvider.displayName = 'FilesProvider';
export { FilesProvider, useFilesContext };
+20
View File
@@ -155,6 +155,18 @@ export enum LocalizationKey {
* View
*/
commonView = 'common.view',
/**
* Translate
*/
commonTranslate = 'common.translate',
/**
* Languages
*/
commonLanguages = 'common.languages',
/**
* Scripts
*/
commonScripts = 'common.scripts',
/**
* Loading content
*/
@@ -487,6 +499,14 @@ export enum LocalizationKey {
* All
*/
dashboardFiltersLanguageFilterAll = 'dashboard.filters.languageFilter.all',
/**
* Delete selected files
*/
dashboardHeaderActionsBarAlertDeleteTitle = 'dashboard.header.actionsBar.alertDelete.title',
/**
* Are you sure you want to delete the selected files?
*/
dashboardHeaderActionsBarAlertDeleteDescription = 'dashboard.header.actionsBar.alertDelete.description',
/**
* Home
*/