From ec9f55b98223d024fba972940f0571d624935682 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 13 Mar 2024 11:32:42 +0100 Subject: [PATCH] Localization actions --- l10n/bundle.l10n.json | 6 + .../components/Contents/Contents.tsx | 25 ++- .../components/Header/ActionsBar.tsx | 114 ++++++++-- .../components/Media/Item.tsx | 2 + .../components/Media/Media.tsx | 195 +++++++++--------- .../providers/FilesProvider.tsx | 36 ++++ src/localization/localization.enum.ts | 20 ++ 7 files changed, 270 insertions(+), 128 deletions(-) create mode 100644 src/dashboardWebView/providers/FilesProvider.tsx diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index be31804e..130062ae 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -38,6 +38,9 @@ "common.open": "Open", "common.openWithValue": "Open: {0}", "common.view": "View", + "common.translate": "Translate", + "common.languages": "Languages", + "common.scripts": "Scripts", "loading.initPages": "Loading content", @@ -147,6 +150,9 @@ "dashboard.filters.languageFilter.label": "Locale", "dashboard.filters.languageFilter.all": "All", + "dashboard.header.actionsBar.alertDelete.title": "Delete selected files", + "dashboard.header.actionsBar.alertDelete.description": "Are you sure you want to delete the selected files?", + "dashboard.header.breadcrumb.home": "Home", "dashboard.header.clearFilters.title": "Clear filters, grouping, and sorting", diff --git a/src/dashboardWebView/components/Contents/Contents.tsx b/src/dashboardWebView/components/Contents/Contents.tsx index 0db5a4df..625e1af1 100644 --- a/src/dashboardWebView/components/Contents/Contents.tsx +++ b/src/dashboardWebView/components/Contents/Contents.tsx @@ -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 = ({ }, []); return ( - -
- {loading ? : } -
+ + +
+ {loading ? : } +
- + - Content metrics -
+ Content metrics +
+ ); }; diff --git a/src/dashboardWebView/components/Header/ActionsBar.tsx b/src/dashboardWebView/components/Header/ActionsBar.tsx index f5a28feb..0c8dfd2d 100644 --- a/src/dashboardWebView/components/Header/ActionsBar.tsx +++ b/src/dashboardWebView/components/Header/ActionsBar.tsx @@ -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 = ({ 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 = ({ } }, [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( + { + messageHandler.send(GeneralCommands.toVSCode.runCommand, { + command: COMMAND_NAME.i18n.create, + args: selectedItem + }) + }}> + + {l10n.t(LocalizationKey.commonTranslate)} + + ) + + 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( + + + + {l10n.t(LocalizationKey.commonLanguages)} + + + + + + messageHandler.send(DashboardMessage.openFile, crntLocale.path)}> + {crntLocale.locale.title || crntLocale.locale.locale} + + + + + { + otherLocales.map(([key, value]) => ( + messageHandler.send(DashboardMessage.openFile, value.path)} + > + {value.locale.title || value.locale.locale} + + )) + } + + + ) + } + } + } + } + + return actions; + }, [files, selectedFiles]); + const customScriptActions = React.useMemo(() => { if (!settings?.scripts) { return null; @@ -80,7 +156,7 @@ export const ActionsBar: React.FunctionComponent = ({ disabled={selectedFiles.length === 0} > - Scripts + {l10n.t(LocalizationKey.commonScripts)} @@ -111,19 +187,13 @@ export const ActionsBar: React.FunctionComponent = ({ aria-label="Item actions" >
- { - view === NavigationType.Contents && ( - <> - 1} - onClick={viewFile} - > - - - ) - } + 1} + onClick={viewFile} + > + { view === NavigationType.Media && ( @@ -142,6 +212,8 @@ export const ActionsBar: React.FunctionComponent = ({ ) } + {languageActions} + {customScriptActions} = ({ {showAlert && ( setShowAlert(false)} diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 8789a647..cc84aae9 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -313,6 +313,8 @@ export const Item: React.FunctionComponent = ({
)} + + )} diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index a7aff12f..07f841f0 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -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 = ( }); return ( - -
- {viewData?.data?.filePath && ( -
-

{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}

-

- {l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)} -

-
- )} - - {isDragActive && ( -
- -

- {selectedFolder - ? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder) - : l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')} -

-
- )} - - {allMedia.length === 0 && folders.length === 0 && !loading && ( -
-
- - -

- {l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)} + + +

+ {viewData?.data?.filePath && ( +
+

{l10n.t(LocalizationKey.dashboardMediaMediaDescription)}

+

+ {l10n.t(LocalizationKey.dashboardMediaMediaDragAndDrop)}

-
- )} - - {contentFolders && - contentFolders.length > 0 && - contentFolders.map( - (group, idx) => - group.folders && - group.folders.length > 0 && ( -
-

- {l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: {group.title} -

- - - {group.folders.map((folder) => ( - - ))} - -
- ) )} - {publicFolders && publicFolders.length > 0 && ( -
- {contentFolders && contentFolders.length > 0 && ( -

- {l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)} - {currentStaticFolder && ( - - : {currentStaticFolder} - - )} -

+ {isDragActive && ( +
+ +

+ {selectedFolder + ? l10n.t(LocalizationKey.dashboardMediaMediaFolderUpload, selectedFolder) + : l10n.t(LocalizationKey.dashboardMediaMediaFolderDefault, currentStaticFolder || 'public')} +

+
+ )} + + {allMedia.length === 0 && folders.length === 0 && !loading && ( +
+
+ + +

+ {l10n.t(LocalizationKey.dashboardMediaMediaPlaceholder)} +

+
+
+ )} + + {contentFolders && + contentFolders.length > 0 && + contentFolders.map( + (group, idx) => + group.folders && + group.folders.length > 0 && ( +
+

+ {l10n.t(LocalizationKey.dashboardMediaMediaContentFolder)}: {group.title} +

+ + + {group.folders.map((folder) => ( + + ))} + +
+ ) )} - - {publicFolders.map((folder) => ( - - ))} - -
- )} + {publicFolders && publicFolders.length > 0 && ( +
+ {contentFolders && contentFolders.length > 0 && ( +

+ {l10n.t(LocalizationKey.dashboardMediaMediaPublicFolder)} + {currentStaticFolder && ( + + : {currentStaticFolder} + + )} +

+ )} - - {allMedia.map((file, idx) => ( - - ))} - -
+ + {publicFolders.map((folder) => ( + + ))} + +
+ )} - + + {allMedia.map((file, idx) => ( + + ))} + +
- {loading && } + - + {loading && } - + - Media metrics - + + + Media metrics + + ); }; diff --git a/src/dashboardWebView/providers/FilesProvider.tsx b/src/dashboardWebView/providers/FilesProvider.tsx new file mode 100644 index 00000000..64639e85 --- /dev/null +++ b/src/dashboardWebView/providers/FilesProvider.tsx @@ -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(undefined); + +const FilesProvider: React.FunctionComponent = ({ files, children }: React.PropsWithChildren) => { + return ( + + {children} + + ) +}; + +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 }; \ No newline at end of file diff --git a/src/localization/localization.enum.ts b/src/localization/localization.enum.ts index 64b0ff31..ec291ce3 100644 --- a/src/localization/localization.enum.ts +++ b/src/localization/localization.enum.ts @@ -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 */