mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-06 01:41:48 +02:00
Localization actions
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user