diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 523f8d44..62638a3d 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -229,6 +229,9 @@ "dashboard.media.folderCreation.hexo.create": "Create post asset folder", "dashboard.media.folderCreation.folder.create": "Create new folder", + "dashboard.media.folderItem.contentDirectory": "Content directory", + "dashboard.media.folderItem.publicDirectory": "Public directory", + "dashboard.media.item.buttom.insert.image": "Insert image", "dashboard.media.item.buttom.insert.snippet": "Insert snippet", diff --git a/package-lock.json b/package-lock.json index 22cdabfe..f2b2f01d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", - "recoil": "^0.4.1", + "recoil": "^0.7.7", "remark-gfm": "^3.0.1", "rimraf": "^3.0.2", "semver": "^7.3.8", @@ -10193,9 +10193,9 @@ } }, "node_modules/recoil": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz", - "integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", "dev": true, "dependencies": { "hamt_plus": "1.0.2" diff --git a/package.json b/package.json index 538d2952..cf314eba 100644 --- a/package.json +++ b/package.json @@ -2816,7 +2816,7 @@ "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", - "recoil": "^0.4.1", + "recoil": "^0.7.7", "remark-gfm": "^3.0.1", "rimraf": "^3.0.2", "semver": "^7.3.8", diff --git a/src/dashboardWebView/components/Header/ActionsBar.tsx b/src/dashboardWebView/components/Header/ActionsBar.tsx new file mode 100644 index 00000000..f309d967 --- /dev/null +++ b/src/dashboardWebView/components/Header/ActionsBar.tsx @@ -0,0 +1,159 @@ +import * as React from 'react'; +import { NavigationType } from '../../models'; +import { CommandLineIcon, PencilIcon, TrashIcon, ChevronDownIcon, XMarkIcon } from '@heroicons/react/24/outline'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { MultiSelectedItemsAtom, SelectedItemActionAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state'; +import { ActionsBarItem } from './ActionsBarItem'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../../localization'; +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'; + +export interface IActionsBarProps { + view: NavigationType; +} + +export const ActionsBar: React.FunctionComponent = ({ + view +}: React.PropsWithChildren) => { + const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom); + const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom); + const [showAlert, setShowAlert] = React.useState(false); + const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); + const settings = useRecoilValue(SettingsSelector); + + const onDeleteConfirm = React.useCallback(() => { + for (const file of selectedFiles) { + if (file) { + if (view === NavigationType.Contents) { + messageHandler.send(DashboardMessage.deleteFile, file); + } else if (view === NavigationType.Media) { + messageHandler.send(DashboardMessage.deleteMedia, { + file: file, + folder: selectedFolder + }); + } + } + } + setSelectedFiles([]); + setShowAlert(false); + }, [selectedFiles]); + + const runCustomScript = React.useCallback((script: CustomScript) => { + for (const file of selectedFiles) { + messageHandler.send(DashboardMessage.runCustomScript, { + script, + path: file + }); + } + + setSelectedFiles([]); + }, [selectedFiles]); + + const customScriptActions = React.useMemo(() => { + if (!settings?.scripts) { + return null; + } + + const { scripts } = settings; + if (view === NavigationType.Media) { + const mediaScripts = (scripts || []) + .filter((script) => script.type === ScriptType.MediaFile && !script.hidden); + + if (mediaScripts.length > 0) { + return ( + + + + Scripts + + + + + { + mediaScripts.map((script) => ( + runCustomScript(script)} + > + + {script.title} + + )) + } + + + ) + } + } + + return null; + }, [view, settings?.scripts, selectedFiles]); + + return ( + <> +
+ { + view === NavigationType.Media && ( +
+ 1} + onClick={() => setSelectedItemAction({ + path: selectedFiles[0], + action: 'edit' + })} + > + + + {customScriptActions} + + setShowAlert(true)} + > + +
+ ) + } + + { + selectedFiles.length > 0 && ( + + ) + } +
+ + {showAlert && ( + setShowAlert(false)} + trigger={onDeleteConfirm} + /> + )} + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/ActionsBarItem.tsx b/src/dashboardWebView/components/Header/ActionsBarItem.tsx new file mode 100644 index 00000000..91a34e43 --- /dev/null +++ b/src/dashboardWebView/components/Header/ActionsBarItem.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { cn } from '../../../utils/cn'; + +export interface IActionsBarItemProps { + className?: string; + disabled?: boolean; + onClick?: () => void; +} + +export const ActionsBarItem: React.FunctionComponent = ({ + children, + className, + disabled, + onClick +}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Breadcrumb.tsx b/src/dashboardWebView/components/Header/Breadcrumb.tsx index e3eb9540..e3ff65c6 100644 --- a/src/dashboardWebView/components/Header/Breadcrumb.tsx +++ b/src/dashboardWebView/components/Header/Breadcrumb.tsx @@ -4,24 +4,25 @@ import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { HOME_PAGE_NAVIGATION_ID } from '../../../constants'; import { parseWinPath } from '../../../helpers/parseWinPath'; -import { SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../../state'; +import { SearchAtom, SettingsAtom } from '../../state'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import useMediaFolder from '../../hooks/useMediaFolder'; export interface IBreadcrumbProps { } export const Breadcrumb: React.FunctionComponent = ( _: React.PropsWithChildren ) => { - const [selectedFolder, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); + const { selectedFolder, updateFolder } = useMediaFolder(); const [, setSearchValue] = useRecoilState(SearchAtom); const [folders, setFolders] = React.useState([]); const settings = useRecoilValue(SettingsAtom); - const updateFolder = (folder: string) => { + const updateMediaFolder = React.useCallback((folder: string) => { setSearchValue(''); - setSelectedFolder(folder); - }; + updateFolder(folder); + }, [updateFolder, setSearchValue]); React.useEffect(() => { if (!settings) { @@ -79,11 +80,11 @@ export const Breadcrumb: React.FunctionComponent = ( }, [selectedFolder, settings]); return ( -
    +
    1. diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index bf1a45e2..984af354 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -6,7 +6,7 @@ import { DashboardMessage } from '../../DashboardMessage'; import { Grouping } from '.'; import { ViewSwitch } from './ViewSwitch'; import { useRecoilValue, useResetRecoilState } from 'recoil'; -import { GroupingSelector, SortingAtom } from '../../state'; +import { GroupingSelector, MultiSelectedItemsAtom, SortingAtom } from '../../state'; import { Messenger } from '@estruyf/vscode/dist/client'; import { ClearFilters } from './ClearFilters'; import { MediaHeaderTop } from '../Media/MediaHeaderTop'; @@ -18,8 +18,7 @@ import { ArrowTopRightOnSquareIcon, BoltIcon, PlusIcon } from '@heroicons/react/ import { HeartIcon } from '@heroicons/react/24/solid'; import { useLocation, useNavigate } from 'react-router-dom'; import { routePaths } from '../..'; -import { useEffect, useMemo } from 'react'; -import { SyncButton } from './SyncButton'; +import { useMemo } from 'react'; import { Pagination } from './Pagination'; import { GroupOption } from '../../constants/GroupOption'; import usePagination from '../../hooks/usePagination'; @@ -32,6 +31,7 @@ import { SettingsLink } from '../SettingsView/SettingsLink'; import { Link } from '../Common/Link'; import { SPONSOR_LINK } from '../../../constants'; import { Filters } from './Filters'; +import { ActionsBar } from './ActionsBar'; export interface IHeaderProps { header?: React.ReactNode; @@ -51,6 +51,7 @@ export const Header: React.FunctionComponent = ({ }: React.PropsWithChildren) => { const grouping = useRecoilValue(GroupingSelector); const resetSorting = useResetRecoilState(SortingAtom); + const resetSelectedItems = useResetRecoilState(MultiSelectedItemsAtom); const location = useLocation(); const navigate = useNavigate(); const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination); @@ -70,6 +71,7 @@ export const Header: React.FunctionComponent = ({ const updateView = (view: NavigationType) => { navigate(routePaths[view]); resetSorting(); + resetSelectedItems(); }; const runBulkScript = (script: CustomScript) => { @@ -216,6 +218,8 @@ export const Header: React.FunctionComponent = ({ + + )} diff --git a/src/dashboardWebView/components/Media/FolderCreation.tsx b/src/dashboardWebView/components/Media/FolderCreation.tsx index 410eae5e..007b3b68 100644 --- a/src/dashboardWebView/components/Media/FolderCreation.tsx +++ b/src/dashboardWebView/components/Media/FolderCreation.tsx @@ -5,7 +5,6 @@ import { DashboardMessage } from '../../DashboardMessage'; import { AllContentFoldersAtom, AllStaticFoldersAtom, - SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state'; @@ -18,13 +17,14 @@ import { extname } from 'path'; import { parseWinPath } from '../../../helpers/parseWinPath'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import useMediaFolder from '../../hooks/useMediaFolder'; export interface IFolderCreationProps { } export const FolderCreation: React.FunctionComponent = ( - props: React.PropsWithChildren + _: React.PropsWithChildren ) => { - const selectedFolder = useRecoilValue(SelectedMediaFolderAtom); + const { selectedFolder } = useMediaFolder(); const settings = useRecoilValue(SettingsSelector); const allStaticFolders = useRecoilValue(AllStaticFoldersAtom); const allContentFolders = useRecoilValue(AllContentFoldersAtom); diff --git a/src/dashboardWebView/components/Media/FolderItem.tsx b/src/dashboardWebView/components/Media/FolderItem.tsx index 00e6b158..7216f9af 100644 --- a/src/dashboardWebView/components/Media/FolderItem.tsx +++ b/src/dashboardWebView/components/Media/FolderItem.tsx @@ -1,8 +1,9 @@ import { FolderIcon } from '@heroicons/react/24/solid'; import { basename, join } from 'path'; import * as React from 'react'; -import { useRecoilState } from 'recoil'; -import { SelectedMediaFolderAtom } from '../../state'; +import * as l10n from '@vscode/l10n'; +import { LocalizationKey } from '../../../localization'; +import useMediaFolder from '../../hooks/useMediaFolder'; export interface IFolderItemProps { folder: string; @@ -15,7 +16,7 @@ export const FolderItem: React.FunctionComponent = ({ wsFolder, staticFolder }: React.PropsWithChildren) => { - const [, setSelectedFolder] = useRecoilState(SelectedMediaFolderAtom); + const { updateFolder } = useMediaFolder(); const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder; @@ -29,9 +30,9 @@ export const FolderItem: React.FunctionComponent = ({ className={`group relative hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`} >