From 0a8723c5440d5ec3122f0fc96aace3ce0d1388e9 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 15 Feb 2024 13:00:45 +0100 Subject: [PATCH] New dropdown implementation --- package-lock.json | 2 - package.json | 2 - src/components/shadcn/Dropdown.tsx | 4 +- .../components/Common/ChoiceButton.tsx | 71 +++--- .../components/Contents/ContentActions.tsx | 118 ++++------ .../components/Header/Filter.tsx | 60 +++-- .../components/Header/FoldersFilter.tsx | 42 ++-- .../components/Header/Grouping.tsx | 34 ++- .../components/Header/ProjectSwitcher.tsx | 16 +- .../components/Header/Sorting.tsx | 42 ++-- .../components/Media/Item.tsx | 214 ++---------------- .../components/Media/ItemMenu.tsx | 191 ++++++++++++++++ .../components/Menu/MenuButton.tsx | 21 +- .../components/Menu/MenuItem.tsx | 20 +- .../components/Menu/MenuItems.tsx | 38 ---- src/dashboardWebView/components/Menu/index.ts | 1 - src/dashboardWebView/utils/getRelPath.ts | 20 ++ src/dashboardWebView/utils/index.ts | 1 + 18 files changed, 425 insertions(+), 472 deletions(-) create mode 100644 src/dashboardWebView/components/Media/ItemMenu.tsx delete mode 100644 src/dashboardWebView/components/Menu/MenuItems.tsx create mode 100644 src/dashboardWebView/utils/getRelPath.ts diff --git a/package-lock.json b/package-lock.json index 6db2767f..70055ee9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@heroicons/react": "^2.1.1", "@iarna/toml": "2.2.3", "@octokit/rest": "^18.12.0", - "@popperjs/core": "^2.11.6", "@sentry/react": "^6.19.7", "@sentry/tracing": "^6.19.7", "@tailwindcss/forms": "^0.5.3", @@ -83,7 +82,6 @@ "react-dom": "17.0.1", "react-dropzone": "^11.7.1", "react-markdown": "^8.0.7", - "react-popper": "^2.3.0", "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", diff --git a/package.json b/package.json index 6577a0c5..5f9cafde 100644 --- a/package.json +++ b/package.json @@ -2686,7 +2686,6 @@ "@heroicons/react": "^2.1.1", "@iarna/toml": "2.2.3", "@octokit/rest": "^18.12.0", - "@popperjs/core": "^2.11.6", "@sentry/react": "^6.19.7", "@sentry/tracing": "^6.19.7", "@tailwindcss/forms": "^0.5.3", @@ -2750,7 +2749,6 @@ "react-dom": "17.0.1", "react-dropzone": "^11.7.1", "react-markdown": "^8.0.7", - "react-popper": "^2.3.0", "react-quill": "^2.0.0", "react-router-dom": "^6.8.0", "react-sortable-hoc": "^2.0.0", diff --git a/src/components/shadcn/Dropdown.tsx b/src/components/shadcn/Dropdown.tsx index 59b38f00..12832787 100644 --- a/src/components/shadcn/Dropdown.tsx +++ b/src/components/shadcn/Dropdown.tsx @@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border border-[var(--frontmatter-border)] bg-[var(--vscode-sideBar-background)] p-1 text-[var(--vscode-editor-foreground)] shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 min-w-[8rem] rounded-md border border-[var(--frontmatter-border)] bg-[var(--vscode-sideBar-background)] p-1 text-[var(--vscode-editor-foreground)] shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-96 overflow-auto", className )} {...props} @@ -83,7 +83,7 @@ const DropdownMenuItem = React.forwardRef< = ({ {title} - {choices.length > 0 && ( - - - {l10n.t(LocalizationKey.dashboardCommonChoiceButtonOpen)} - - -
- {choices.map((choice, idx) => ( - - {choice.icon} - {choice.title} -
- ) : ( - choice.title - ) - } - value={null} - onClick={choice.onClick} - disabled={choice.disabled} - /> - ))} - -
-
- )} - + {choices.length > 0 && ( + + + {l10n.t(LocalizationKey.dashboardCommonChoiceButtonOpen)} + + + + {choices.map((choice, idx) => ( + + {choice.icon} + {choice.title} + + ) : ( + choice.title + ) + } + value={null} + onClick={choice.onClick} + disabled={choice.disabled} + /> + ))} + + + ) + } + ); }; diff --git a/src/dashboardWebView/components/Contents/ContentActions.tsx b/src/dashboardWebView/components/Contents/ContentActions.tsx index 912eaf32..4c78187d 100644 --- a/src/dashboardWebView/components/Contents/ContentActions.tsx +++ b/src/dashboardWebView/components/Contents/ContentActions.tsx @@ -1,13 +1,11 @@ import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; import { Menu } from '@headlessui/react'; -import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { EyeIcon, GlobeEuropeAfricaIcon, CommandLineIcon, TrashIcon, EllipsisVerticalIcon } from '@heroicons/react/24/outline'; import * as React from 'react'; import { CustomScript, ScriptType } from '../../../models'; import { DashboardMessage } from '../../DashboardMessage'; -import { MenuItem, MenuItems, ActionMenuButton, QuickAction } from '../Menu'; +import { QuickAction } from '../Menu'; import { Alert } from '../Modals/Alert'; -import { usePopper } from 'react-popper'; -import { useState } from 'react'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; import { useRecoilState, useRecoilValue } from 'recoil'; @@ -15,6 +13,7 @@ import { SettingsSelector } from '../../state'; import { GeneralCommands } from '../../../constants'; import { PinIcon } from '../Icons/PinIcon'; import { PinnedItemsAtom } from '../../state/atom/PinnedItems'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; export interface IContentActionsProps { title: string; @@ -37,19 +36,12 @@ export const ContentActions: React.FunctionComponent = ({ const [showDeletionAlert, setShowDeletionAlert] = React.useState(false); const settings = useRecoilValue(SettingsSelector); - const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - const { styles, attributes, forceUpdate } = usePopper(referenceElement, popperElement, { - placement: listView ? 'right-start' : 'bottom-end', - strategy: 'fixed' - }); - - const onView = (e: React.MouseEvent) => { + const onView = (e: React.MouseEvent) => { e.stopPropagation(); onOpen(); }; - const onDelete = (e: React.MouseEvent) => { + const onDelete = (e: React.MouseEvent) => { e.stopPropagation(); setShowDeletionAlert(true); }; @@ -61,7 +53,7 @@ export const ContentActions: React.FunctionComponent = ({ setShowDeletionAlert(false); }; - const openOnWebsite = React.useCallback((e: React.MouseEvent) => { + const openOnWebsite = React.useCallback((e: React.MouseEvent) => { e.stopPropagation(); if (settings?.websiteUrl && path) { Messenger.send(GeneralCommands.toVSCode.openOnWebsite, { @@ -71,14 +63,14 @@ export const ContentActions: React.FunctionComponent = ({ } }, [settings?.websiteUrl, path]); - const pinItem = React.useCallback((e: React.MouseEvent) => { + const pinItem = React.useCallback((e: React.MouseEvent) => { e.stopPropagation(); messageHandler.request(DashboardMessage.pinItem, path).then((result) => { setPinnedItems(result || []); }) }, [path]); - const unpinItem = React.useCallback((e: React.MouseEvent) => { + const unpinItem = React.useCallback((e: React.MouseEvent) => { e.stopPropagation(); messageHandler.request(DashboardMessage.unpinItem, path).then((result) => { setPinnedItems(result || []); @@ -86,7 +78,7 @@ export const ContentActions: React.FunctionComponent = ({ }, [path]); const runCustomScript = React.useCallback( - (e: React.MouseEvent, script: CustomScript) => { + (e: React.MouseEvent, script: CustomScript) => { e.stopPropagation(); Messenger.send(DashboardMessage.runCustomScript, { script, path }); }, @@ -106,16 +98,10 @@ export const ContentActions: React.FunctionComponent = ({ !script.hidden ) .map((script) => ( - - {' '} - {script.title} - - } - onClick={(value, e) => runCustomScript(e, script)} - /> + runCustomScript(e, script)}> + + {script.title} + )); }, [scripts]); @@ -150,67 +136,41 @@ export const ContentActions: React.FunctionComponent = ({ )} -
- -
+ + + {l10n.t(LocalizationKey.dashboardContentsContentActionsActionMenuButtonTitle)} + -
- - - {' '} - {isPinned ? l10n.t(LocalizationKey.commonUnpin) : l10n.t(LocalizationKey.commonPin)} -
- } - onClick={(_, e) => isPinned ? unpinItem(e) : pinItem(e)} - /> - - {' '} - {l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} - - } - onClick={(_, e) => onView(e)} - /> + + isPinned ? unpinItem(e) : pinItem(e)}> + + {isPinned ? l10n.t(LocalizationKey.commonUnpin) : l10n.t(LocalizationKey.commonPin)} + + + + + {l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)} + { settings?.websiteUrl && ( - - {' '} - {l10n.t(LocalizationKey.commonOpenOnWebsite)} - - } - onClick={(_, e) => openOnWebsite(e)} - /> + + + {l10n.t(LocalizationKey.commonOpenOnWebsite)} + ) } {customScriptActions} - - {' '} - {l10n.t(LocalizationKey.commonDelete)} - - } - onClick={(_, e) => onDelete(e)} - /> - - + + + {l10n.t(LocalizationKey.commonDelete)} + + + +
diff --git a/src/dashboardWebView/components/Header/Filter.tsx b/src/dashboardWebView/components/Header/Filter.tsx index 3b06944e..34a34bfe 100644 --- a/src/dashboardWebView/components/Header/Filter.tsx +++ b/src/dashboardWebView/components/Header/Filter.tsx @@ -1,9 +1,9 @@ -import { Menu } from '@headlessui/react'; -import { FunnelIcon } from '@heroicons/react/24/solid'; import * as React from 'react'; -import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { MenuButton, MenuItem } from '../Menu'; import * as l10n from '@vscode/l10n'; +import { FunnelIcon } from '@heroicons/react/24/solid'; import { LocalizationKey } from '../../../localization'; +import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown'; export interface IFilterProps { label: string; @@ -25,37 +25,35 @@ export const Filter: React.FunctionComponent = ({ } return ( -
- - - - {label} - - } - title={activeItem || DEFAULT_VALUE} + + + + {label} + + } + title={activeItem || DEFAULT_VALUE} + /> + + + onClick(null)} /> - + {items.map((option) => ( onClick(null)} + key={option} + title={option} + value={option} + isCurrent={option === activeItem} + onClick={() => onClick(option)} /> - - {items.map((option) => ( - onClick(option)} - /> - ))} - - -
+ ))} + + ); }; diff --git a/src/dashboardWebView/components/Header/FoldersFilter.tsx b/src/dashboardWebView/components/Header/FoldersFilter.tsx index 56e482a1..c683cb77 100644 --- a/src/dashboardWebView/components/Header/FoldersFilter.tsx +++ b/src/dashboardWebView/components/Header/FoldersFilter.tsx @@ -1,10 +1,10 @@ -import { Menu } from '@headlessui/react'; import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { FolderAtom, SettingsSelector } from '../../state'; -import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { MenuButton, MenuItem } from '../Menu'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; export interface IFoldersFilterProps { } @@ -21,29 +21,27 @@ export const FoldersFilter: React.FunctionComponent< } return ( -
- - + + - + + setCrntFolder(value)} + /> + + {contentFolders.map((option) => ( setCrntFolder(value)} /> - - {contentFolders.map((option) => ( - setCrntFolder(value)} - /> - ))} - - -
+ ))} + + ); }; diff --git a/src/dashboardWebView/components/Header/Grouping.tsx b/src/dashboardWebView/components/Header/Grouping.tsx index 61c35d0c..8073ae3c 100644 --- a/src/dashboardWebView/components/Header/Grouping.tsx +++ b/src/dashboardWebView/components/Header/Grouping.tsx @@ -1,11 +1,11 @@ -import { Menu } from '@headlessui/react'; import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { GroupOption } from '../../constants/GroupOption'; import { AllPagesAtom, GroupingAtom } from '../../state'; -import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { MenuButton, MenuItem } from '../Menu'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown'; export interface IGroupingProps { } @@ -42,22 +42,20 @@ export const Grouping: React.FunctionComponent< } return ( -
- - + + - - {GROUP_OPTIONS.map((option) => ( - setGroup(value)} - /> - ))} - - -
+ + {GROUP_OPTIONS.map((option) => ( + setGroup(value)} + /> + ))} + + ); }; diff --git a/src/dashboardWebView/components/Header/ProjectSwitcher.tsx b/src/dashboardWebView/components/Header/ProjectSwitcher.tsx index 7f35e4b4..10ec4488 100644 --- a/src/dashboardWebView/components/Header/ProjectSwitcher.tsx +++ b/src/dashboardWebView/components/Header/ProjectSwitcher.tsx @@ -1,13 +1,13 @@ import { messageHandler } from '@estruyf/vscode/dist/client'; -import { Menu } from '@headlessui/react'; import { ArrowsRightLeftIcon } from '@heroicons/react/24/outline'; import * as React from 'react'; import { useRecoilValue } from 'recoil'; import { DashboardMessage } from '../../DashboardMessage'; import { SettingsSelector } from '../../state'; -import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { MenuButton, MenuItem } from '../Menu'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown'; export interface IProjectSwitcherProps { } @@ -32,8 +32,8 @@ export const ProjectSwitcher: React.FunctionComponent = ( } return ( -
- +
+ @@ -43,7 +43,7 @@ export const ProjectSwitcher: React.FunctionComponent = ( )} title={crntProject} /> - + {projects.map((p) => ( = ( onClick={(value) => setProject(p.name)} /> ))} - -
-
+ + + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Sorting.tsx b/src/dashboardWebView/components/Header/Sorting.tsx index 9249fe13..a8a49e18 100644 --- a/src/dashboardWebView/components/Header/Sorting.tsx +++ b/src/dashboardWebView/components/Header/Sorting.tsx @@ -1,5 +1,4 @@ import { Messenger, messageHandler } from '@estruyf/vscode/dist/client'; -import { Menu } from '@headlessui/react'; import * as React from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { ExtensionState } from '../../../constants'; @@ -9,10 +8,11 @@ import { DashboardMessage } from '../../DashboardMessage'; import { NavigationType } from '../../models'; import { SortingOption } from '../../models/SortingOption'; import { SearchSelector, SettingsSelector, SortingAtom } from '../../state'; -import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { MenuButton, MenuItem } from '../Menu'; import { Sorting as SortingHelpers } from '../../../helpers/Sorting'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; +import { DropdownMenu, DropdownMenuContent } from '../../../components/shadcn/Dropdown'; export interface ISortingProps { disableCustomSorting?: boolean; @@ -177,26 +177,24 @@ export const Sorting: React.FunctionComponent = ({ } return ( -
- - + + - - {allOptions.map((option) => ( - updateSorting(value)} - /> - ))} - - -
+ + {allOptions.map((option) => ( + updateSorting(value)} + /> + ))} + + ); }; diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index c280ce86..0831418a 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -1,27 +1,18 @@ import { Messenger } from '@estruyf/vscode/dist/client'; -import { Menu } from '@headlessui/react'; import { - ClipboardIcon, CodeBracketIcon, DocumentIcon, - EyeIcon, MusicalNoteIcon, - PencilIcon, PhotoIcon, PlusIcon, - CommandLineIcon, - TrashIcon, VideoCameraIcon, - EllipsisVerticalIcon } from '@heroicons/react/24/outline'; import { basename, dirname } from 'path'; import * as React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { CustomScript } from '../../../helpers/CustomScript'; import { parseWinPath } from '../../../helpers/parseWinPath'; import { SnippetParser } from '../../../helpers/SnippetParser'; -import { ScriptType, Snippet } from '../../../models'; import { MediaInfo } from '../../../models/MediaPaths'; import { DashboardMessage } from '../../DashboardMessage'; import { @@ -30,14 +21,15 @@ import { SettingsSelector, ViewDataSelector } from '../../state'; -import { QuickAction } from '../Menu/QuickAction'; import { Alert } from '../Modals/Alert'; import { InfoDialog } from '../Modals/InfoDialog'; import { DetailsSlideOver } from './DetailsSlideOver'; import { MediaSnippetForm } from './MediaSnippetForm'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../../../localization'; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { ItemMenu } from './ItemMenu'; +import { getRelPath } from '../../utils'; +import { Snippet } from '../../../models'; export interface IItemProps { media: MediaInfo; @@ -59,6 +51,10 @@ export const Item: React.FunctionComponent = ({ const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); const viewData = useRecoilValue(ViewDataSelector); + const relPath = useMemo(() => { + return getRelPath(media.fsPath, settings?.staticFolder, settings?.wsFolder); + }, [media.fsPath, settings?.staticFolder, settings?.wsFolder]); + const hasViewData = useMemo(() => { return viewData?.data?.filePath !== undefined; }, [viewData]); @@ -91,44 +87,11 @@ export const Item: React.FunctionComponent = ({ return ''; }; - const getRelPath = () => { - let relPath: string | undefined = ''; - if (settings?.wsFolder && media.fsPath) { - const wsFolderParsed = parseWinPath(settings.wsFolder); - const mediaParsed = parseWinPath(media.fsPath); - - relPath = mediaParsed.split(wsFolderParsed).pop(); - - // If the static folder is the root, we can just return the relative path - if (settings.staticFolder === "/") { - return relPath; - } else if (settings.staticFolder && relPath) { - const staticFolderParsed = parseWinPath(settings.staticFolder); - relPath = relPath.split(staticFolderParsed).pop(); - } - } - return relPath; - }; - const getFileName = () => { return basename(parseWinPath(media.fsPath) || ''); }; - const copyToClipboard = () => { - const relPath = getRelPath(); - Messenger.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || ''); - }; - - const runCustomScript = (script: CustomScript) => { - Messenger.send(DashboardMessage.runCustomScript, { - script, - path: media.fsPath - }); - }; - - const insertToArticle = () => { - const relPath = getRelPath(); - + const insertIntoArticle = useCallback(() => { if (viewData?.data?.type === 'file') { Messenger.send(DashboardMessage.insertFile, { relPath: parseWinPath(relPath) || '', @@ -158,7 +121,7 @@ export const Item: React.FunctionComponent = ({ title: media.metadata.title || '' }); } - }; + }, [media, settings, viewData, relPath]); const insertSnippet = useCallback(() => { if (mediaSnippets.length === 1) { @@ -176,8 +139,6 @@ export const Item: React.FunctionComponent = ({ (snippet: Snippet) => { setShowSnippetSelection(false); - const relPath = getRelPath(); - const fieldData = { mediaUrl: (parseWinPath(relPath) || '').replace(/ /g, '%20'), filename: basename(relPath || ''), @@ -203,7 +164,7 @@ export const Item: React.FunctionComponent = ({ setMediaData(fieldData); } }, - [media, settings, viewData, mediaSnippets] + [media, settings, viewData, mediaSnippets, relPath] ); /** @@ -211,8 +172,6 @@ export const Item: React.FunctionComponent = ({ */ const insertMediaSnippetToArticle = useCallback( (output: string) => { - const relPath = getRelPath(); - Messenger.send(DashboardMessage.insertMedia, { relPath: parseWinPath(relPath) || '', file: viewData?.data?.filePath, @@ -221,20 +180,9 @@ export const Item: React.FunctionComponent = ({ snippet: output }); }, - [viewData] + [viewData, relPath] ); - const deleteMedia = () => { - setShowAlert(true); - }; - - const revealMedia = () => { - Messenger.send(DashboardMessage.revealMedia, { - file: media.fsPath, - folder: selectedFolder - }); - }; - const confirmDeletion = () => { Messenger.send(DashboardMessage.deleteMedia, { file: media.fsPath, @@ -278,10 +226,6 @@ export const Item: React.FunctionComponent = ({ return sizeDetails.join(' - '); }; - const viewMediaDetails = () => { - setShowDetails(true); - }; - const openLightbox = useCallback(() => { if (isImageFile) { setLightbox(media.vsPath || ''); @@ -293,20 +237,6 @@ export const Item: React.FunctionComponent = ({ setShowDetails(true); }; - const customScriptActions = useMemo(() => { - return (settings?.scripts || []) - .filter((script) => script.type === ScriptType.MediaFile && !script.hidden) - .map((script) => ( - runCustomScript(script)} - > - - {script.title} - - )); - }, [settings?.scripts]); - const isVideoFile = useMemo(() => { if (media.mimeType?.startsWith('video/')) { return true; @@ -430,7 +360,7 @@ export const Item: React.FunctionComponent = ({ @@ -453,114 +383,20 @@ export const Item: React.FunctionComponent = ({ )}
-
-
- -
- - + setShowDetails(true)} + processSnippet={processSnippet} + onDelete={() => setShowAlert(true)} /> - - - - {viewData?.data?.filePath ? ( - <> - - - - {viewData?.data?.position && mediaSnippets.length > 0 && ( - - - )} - - ) : ( - <> - - - - )} - - - -
- - - - {l10n.t(LocalizationKey.commonMenu)} - - - - - - {l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} - - - { - viewData?.data?.filePath ? ( - <> - - - {l10n.t(LocalizationKey.dashboardMediaItemMenuItemInsertImage)} - - - { - viewData?.data?.position && - mediaSnippets.length > 0 && - mediaSnippets.map((snippet, idx) => ( - processSnippet(snippet)}> - - {snippet.title} - - )) - } - - {customScriptActions} - - ) : ( - <> - - - {l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} - - - {customScriptActions} - - ) - } - - - - {l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)} - - - - - {l10n.t(LocalizationKey.commonDelete)} - - - -
-
-

{basename(parseWinPath(media.fsPath) || '')}

diff --git a/src/dashboardWebView/components/Media/ItemMenu.tsx b/src/dashboardWebView/components/Media/ItemMenu.tsx new file mode 100644 index 00000000..8b87cff0 --- /dev/null +++ b/src/dashboardWebView/components/Media/ItemMenu.tsx @@ -0,0 +1,191 @@ +import * as React from 'react'; +import * as l10n from '@vscode/l10n'; +import { Menu } from '@headlessui/react'; +import { LocalizationKey } from '../../../localization'; +import { QuickAction } from '../Menu'; +import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { CustomScript, MediaInfo, ScriptType, Snippet, ViewData } from '../../../models'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; +import { messageHandler } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { parseWinPath } from '../../../helpers/parseWinPath'; + +export interface IItemMenuProps { + media: MediaInfo; + relPath?: string; + selectedFolder: string | null; + viewData?: ViewData; + snippets: Snippet[]; + scripts?: CustomScript[]; + insertIntoArticle: () => void; + insertSnippet: () => void; + showUpdateMedia: () => void; + showMediaDetails: () => void; + processSnippet: (snippet: Snippet) => void; + onDelete: () => void; +} + +export const ItemMenu: React.FunctionComponent = ({ + media, + relPath, + selectedFolder, + viewData, + snippets, + scripts, + insertIntoArticle, + insertSnippet, + showUpdateMedia, + showMediaDetails, + processSnippet, + onDelete, +}: React.PropsWithChildren) => { + + const copyToClipboard = React.useCallback(() => { + if (relPath) { + messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || ''); + } + }, [relPath]); + + const runCustomScript = React.useCallback((script: CustomScript) => { + messageHandler.send(DashboardMessage.runCustomScript, { + script, + path: media.fsPath + }); + }, [media]); + + const revealMedia = React.useCallback(() => { + messageHandler.send(DashboardMessage.revealMedia, { + file: media.fsPath, + folder: selectedFolder + }); + }, [selectedFolder]); + + const customScriptActions = React.useMemo(() => { + return (scripts || []) + .filter((script) => script.type === ScriptType.MediaFile && !script.hidden) + .map((script) => ( + runCustomScript(script)} + > + + {script.title} + + )); + }, [scripts]); + + return ( +
+
+ +
+ + + + + + + {viewData?.filePath ? ( + <> + + + + {viewData?.position && snippets.length > 0 && ( + + + )} + + ) : ( + <> + { + relPath && ( + + + ) + } + + )} + + + +
+ + + + {l10n.t(LocalizationKey.commonMenu)} + + + + + + {l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} + + + { + viewData?.filePath ? ( + <> + + + {l10n.t(LocalizationKey.dashboardMediaItemMenuItemInsertImage)} + + + { + viewData?.position && + snippets.length > 0 && + snippets.map((snippet, idx) => ( + processSnippet(snippet)}> + + {snippet.title} + + )) + } + + {customScriptActions} + + ) : ( + <> + + + {l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} + + + {customScriptActions} + + ) + } + + + + {l10n.t(LocalizationKey.dashboardMediaItemMenuItemRevealMedia)} + + + + + {l10n.t(LocalizationKey.commonDelete)} + + + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Menu/MenuButton.tsx b/src/dashboardWebView/components/Menu/MenuButton.tsx index 947b1b88..30a18179 100644 --- a/src/dashboardWebView/components/Menu/MenuButton.tsx +++ b/src/dashboardWebView/components/Menu/MenuButton.tsx @@ -1,6 +1,7 @@ import { Menu } from '@headlessui/react'; import { ChevronDownIcon } from '@heroicons/react/24/solid'; import * as React from 'react'; +import { DropdownMenuTrigger } from '../../../components/shadcn/Dropdown'; export interface IMenuButtonProps { label: string | JSX.Element; @@ -15,18 +16,16 @@ export const MenuButton: React.FunctionComponent = ({ }: React.PropsWithChildren) => { return (
-
{label}:
+
+ {label}: +
- - {title} - + + {title} +
); }; diff --git a/src/dashboardWebView/components/Menu/MenuItem.tsx b/src/dashboardWebView/components/Menu/MenuItem.tsx index 2c1fe57d..98f2dbb6 100644 --- a/src/dashboardWebView/components/Menu/MenuItem.tsx +++ b/src/dashboardWebView/components/Menu/MenuItem.tsx @@ -1,12 +1,13 @@ import { Menu } from '@headlessui/react'; import * as React from 'react'; +import { DropdownMenuItem } from '../../../components/shadcn/Dropdown'; export interface IMenuItemProps { title: JSX.Element | string; value?: any; isCurrent?: boolean; disabled?: boolean; - onClick: (value: any, e: React.MouseEvent) => void; + onClick: (value: any, e: React.MouseEvent) => void; } export const MenuItem: React.FunctionComponent = ({ @@ -17,15 +18,12 @@ export const MenuItem: React.FunctionComponent = ({ onClick }: React.PropsWithChildren) => { return ( - - - + onClick(value, e)} + > + {title} + ); }; diff --git a/src/dashboardWebView/components/Menu/MenuItems.tsx b/src/dashboardWebView/components/Menu/MenuItems.tsx deleted file mode 100644 index 713f7317..00000000 --- a/src/dashboardWebView/components/Menu/MenuItems.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Menu, Transition } from '@headlessui/react'; -import * as React from 'react'; -import { Fragment } from 'react'; - -export interface IMenuItemsProps { - widthClass?: string; - marginTopClass?: string; - updatePopper?: () => void; - disablePopper?: boolean; -} - -export const MenuItems: React.FunctionComponent = ({ - widthClass, - marginTopClass, - children, - updatePopper, - disablePopper -}: React.PropsWithChildren) => { - return ( - (updatePopper ? updatePopper() : null)} - enter="transition ease-out duration-100" - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leave="transition ease-in duration-75" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - > - -
{children}
-
-
- ); -}; diff --git a/src/dashboardWebView/components/Menu/index.ts b/src/dashboardWebView/components/Menu/index.ts index b903ea66..f17c42d3 100644 --- a/src/dashboardWebView/components/Menu/index.ts +++ b/src/dashboardWebView/components/Menu/index.ts @@ -1,5 +1,4 @@ export * from './ActionMenuButton'; export * from './MenuButton'; export * from './MenuItem'; -export * from './MenuItems'; export * from './QuickAction'; diff --git a/src/dashboardWebView/utils/getRelPath.ts b/src/dashboardWebView/utils/getRelPath.ts new file mode 100644 index 00000000..6f59d2c4 --- /dev/null +++ b/src/dashboardWebView/utils/getRelPath.ts @@ -0,0 +1,20 @@ +import { parseWinPath } from '../../helpers/parseWinPath'; + +export const getRelPath = (path: string, staticFolder?: string, wsFolder?: string) => { + let relPath: string | undefined = ''; + if (wsFolder && path) { + const wsFolderParsed = parseWinPath(wsFolder); + const mediaParsed = parseWinPath(path); + + relPath = mediaParsed.split(wsFolderParsed).pop(); + + // If the static folder is the root, we can just return the relative path + if (staticFolder === '/') { + return relPath; + } else if (staticFolder && relPath) { + const staticFolderParsed = parseWinPath(staticFolder); + relPath = relPath.split(staticFolderParsed).pop(); + } + } + return relPath; +}; diff --git a/src/dashboardWebView/utils/index.ts b/src/dashboardWebView/utils/index.ts index c7dab3e8..9c6fc58a 100644 --- a/src/dashboardWebView/utils/index.ts +++ b/src/dashboardWebView/utils/index.ts @@ -1,2 +1,3 @@ +export * from './getRelPath'; export * from './preserveColor'; export * from './updateCssVariables';