mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-06-24 20:11:02 +02:00
#785 - Media actions
This commit is contained in:
@@ -19,11 +19,12 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
return (
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)]`}>
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]`}>
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => openFile(filePath)}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.dashboardContentsContentActionsMenuItemView)}</span>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
@@ -33,6 +34,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
title={l10n.t(LocalizationKey.commonOpenOnWebsite)}
|
||||
className={`text-[var(--frontmatter-secondary-text)]`}
|
||||
onClick={() => openOnWebsite(websiteUrl, filePath)}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonOpenOnWebsite)}</span>
|
||||
<GlobeEuropeAfricaIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
@@ -42,6 +44,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
title={l10n.t(LocalizationKey.commonDelete)}
|
||||
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={() => setSelectedItemAction({ path: filePath, action: 'delete' })}>
|
||||
<span className={`sr-only`}>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { ClipboardIcon, CodeBracketIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SelectedItemActionAtom } from '../../state';
|
||||
import { MediaInfo, Snippet, ViewData } from '../../../models';
|
||||
import { copyToClipboard } from '../../utils';
|
||||
import { parseWinPath } from '../../../helpers/parseWinPath';
|
||||
|
||||
export interface IFooterActionsProps {
|
||||
media: MediaInfo;
|
||||
snippets: Snippet[];
|
||||
relPath?: string;
|
||||
viewData?: ViewData | undefined
|
||||
insertIntoArticle: () => void;
|
||||
insertSnippet: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
|
||||
relPath,
|
||||
media,
|
||||
snippets,
|
||||
viewData,
|
||||
insertIntoArticle,
|
||||
insertSnippet,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IFooterActionsProps>) => {
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
|
||||
return (
|
||||
<div className={`py-2 w-full flex items-center justify-evenly border-t border-t-[var(--frontmatter-border)] bg-[var(--frontmatter-sideBar-background)] group-hover:bg-[var(--vscode-list-hoverBackground)]`}>
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'view'
|
||||
})}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
|
||||
onClick={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
})}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.commonInsertSnippet)}
|
||||
onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
|
||||
onClick={() => copyToClipboard(parseWinPath(relPath) || '')}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -32,6 +32,7 @@ import { getRelPath } from '../../utils';
|
||||
import { Snippet } from '../../../models';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
import { FooterActions } from './FooterActions';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -185,13 +186,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}
|
||||
}, [media.vsPath]);
|
||||
|
||||
const updateMetadata = useCallback(() => {
|
||||
setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
});
|
||||
}, [media]);
|
||||
|
||||
const renderMediaIcon = useMemo(() => {
|
||||
const path = media.fsPath;
|
||||
const extension = path.split('.').pop();
|
||||
@@ -265,7 +259,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className={`group relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<li className={`group flex flex-col relative shadow-md hover:shadow-xl dark:shadow-none border rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<button
|
||||
className={`group/button relative block w-full aspect-w-10 aspect-h-7 overflow-hidden h-48 ${isImage ? 'cursor-pointer' : 'cursor-default'} border-b border-[var(--frontmatter-border)]`}
|
||||
onClick={hasViewData ? undefined : openLightbox}
|
||||
@@ -318,7 +312,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className={`relative py-4 pl-4 pr-12`}>
|
||||
|
||||
<div className={`relative py-4 pl-4 pr-12 grow`}>
|
||||
<ItemMenu
|
||||
media={media}
|
||||
relPath={relPath}
|
||||
@@ -327,9 +322,14 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
snippets={mediaSnippets}
|
||||
scripts={settings?.scripts}
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
showUpdateMedia={updateMetadata}
|
||||
showMediaDetails={() => setSelectedItemAction({ path: media.fsPath, action: 'view' })}
|
||||
showUpdateMedia={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'edit'
|
||||
})}
|
||||
showMediaDetails={() => setSelectedItemAction({
|
||||
path: media.fsPath,
|
||||
action: 'view'
|
||||
})}
|
||||
processSnippet={processSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
|
||||
@@ -371,6 +371,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FooterActions
|
||||
media={media}
|
||||
relPath={relPath}
|
||||
snippets={mediaSnippets}
|
||||
viewData={viewData?.data}
|
||||
insertIntoArticle={insertIntoArticle}
|
||||
insertSnippet={insertSnippet}
|
||||
onDelete={() => setShowAlert(true)} />
|
||||
</li>
|
||||
|
||||
{showSnippetSelection && (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { QuickAction } from '../Menu';
|
||||
import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisVerticalIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { ClipboardIcon, CodeBracketIcon, CommandLineIcon, EllipsisHorizontalIcon, 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';
|
||||
import { copyToClipboard } from '../../utils';
|
||||
|
||||
export interface IItemMenuProps {
|
||||
media: MediaInfo;
|
||||
@@ -17,7 +17,6 @@ export interface IItemMenuProps {
|
||||
snippets: Snippet[];
|
||||
scripts?: CustomScript[];
|
||||
insertIntoArticle: () => void;
|
||||
insertSnippet: () => void;
|
||||
showUpdateMedia: () => void;
|
||||
showMediaDetails: () => void;
|
||||
processSnippet: (snippet: Snippet) => void;
|
||||
@@ -32,17 +31,14 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
snippets,
|
||||
scripts,
|
||||
insertIntoArticle,
|
||||
insertSnippet,
|
||||
showUpdateMedia,
|
||||
showMediaDetails,
|
||||
processSnippet,
|
||||
onDelete,
|
||||
}: React.PropsWithChildren<IItemMenuProps>) => {
|
||||
|
||||
const copyToClipboard = React.useCallback(() => {
|
||||
if (relPath) {
|
||||
messageHandler.send(DashboardMessage.copyToClipboard, parseWinPath(relPath) || '');
|
||||
}
|
||||
const onCopyToClipboard = React.useCallback(() => {
|
||||
copyToClipboard(parseWinPath(relPath) || '');
|
||||
}, [relPath]);
|
||||
|
||||
const runCustomScript = React.useCallback((script: CustomScript) => {
|
||||
@@ -75,65 +71,20 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`group/actions absolute top-4 right-4 flex flex-col space-y-4`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-2 -mr-2 -mt-2 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<div className={`flex items-center border border-transparent rounded-full p-1 -mr-2 -mt-1 group-hover/actions:bg-[var(--vscode-sideBar-background)] group-hover/actions:border-[var(--frontmatter-border)]`}>
|
||||
<div className="relative z-10 flex text-left">
|
||||
<div className="hidden group-hover/actions:flex">
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)} onClick={showMediaDetails}>
|
||||
<EyeIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
|
||||
</QuickAction>
|
||||
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)} onClick={showUpdateMedia}>
|
||||
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
<span className='sr-only'>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.filePath ? (
|
||||
<>
|
||||
<QuickAction
|
||||
title={
|
||||
viewData.metadataInsert && viewData.fieldName
|
||||
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
|
||||
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
|
||||
}
|
||||
onClick={insertIntoArticle}
|
||||
>
|
||||
<PlusIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
|
||||
{viewData?.position && snippets.length > 0 && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.commonInsertSnippet)} onClick={insertSnippet}>
|
||||
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{
|
||||
relPath && (
|
||||
<QuickAction title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)} onClick={copyToClipboard}>
|
||||
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
<QuickAction
|
||||
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
|
||||
className={`hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
|
||||
onClick={onDelete}>
|
||||
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
|
||||
</QuickAction>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className='text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)]'>
|
||||
<span className="sr-only">{l10n.t(LocalizationKey.commonMenu)}</span>
|
||||
<EllipsisVerticalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
<EllipsisHorizontalIcon className="w-4 h-4" aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={showMediaDetails}>
|
||||
<EyeIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.commonView)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={showUpdateMedia}>
|
||||
<PencilIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
|
||||
@@ -165,7 +116,7 @@ export const ItemMenu: React.FunctionComponent<IItemMenuProps> = ({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuItem onClick={copyToClipboard}>
|
||||
<DropdownMenuItem onClick={onCopyToClipboard}>
|
||||
<ClipboardIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -28,3 +28,11 @@ export const openOnWebsite = (websiteUrl?: string, filePath?: string) => {
|
||||
filePath
|
||||
});
|
||||
};
|
||||
|
||||
export const copyToClipboard = (value: string) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageHandler.send(DashboardMessage.copyToClipboard, value);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
export const darkenColor = (color: string | undefined, percentage: number) => {
|
||||
if (!color) {
|
||||
return color;
|
||||
}
|
||||
|
||||
// Remove any whitespace and convert to lowercase
|
||||
color = color.trim().toLowerCase();
|
||||
|
||||
// Check if the color is in hex format
|
||||
if (color.startsWith('#')) {
|
||||
// Remove the "#" symbol
|
||||
color = color.slice(1);
|
||||
|
||||
// Convert the color to rgb format
|
||||
const hexToRgb = (hex: string) => {
|
||||
const bigint = parseInt(hex, 16);
|
||||
const r = (bigint >> 16) & 255;
|
||||
const g = (bigint >> 8) & 255;
|
||||
const b = bigint & 255;
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
const [r, g, b] = hexToRgb(color);
|
||||
|
||||
// Calculate the darkened color
|
||||
const darkenValue = Math.round(255 * (percentage / 100));
|
||||
const darkenedR = Math.max(r - darkenValue, 0);
|
||||
const darkenedG = Math.max(g - darkenValue, 0);
|
||||
const darkenedB = Math.max(b - darkenValue, 0);
|
||||
|
||||
// Convert the darkened color back to hex format
|
||||
const rgbToHex = (r: number, g: number, b: number) => {
|
||||
const toHex = (c: number) => {
|
||||
const hex = c.toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
};
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
};
|
||||
|
||||
return rgbToHex(darkenedR, darkenedG, darkenedB);
|
||||
}
|
||||
|
||||
// Check if the color is in rgb or rgba format
|
||||
if (color.startsWith('rgb')) {
|
||||
// Extract the RGB values
|
||||
const rgbValues = color.match(/\d+/g);
|
||||
|
||||
if (rgbValues) {
|
||||
const [r, g, b] = rgbValues.map(Number);
|
||||
|
||||
// Calculate the darkened color
|
||||
const darkenValue = Math.round(255 * (percentage / 100));
|
||||
const darkenedR = Math.max(r - darkenValue, 0);
|
||||
const darkenedG = Math.max(g - darkenValue, 0);
|
||||
const darkenedB = Math.max(b - darkenValue, 0);
|
||||
|
||||
// Check if the color is in rgba format
|
||||
if (color.startsWith('rgba')) {
|
||||
// Extract the alpha value
|
||||
const alpha = Number(color.match(/[\d\.]+$/));
|
||||
|
||||
return `rgba(${darkenedR}, ${darkenedG}, ${darkenedB}, ${alpha})`;
|
||||
}
|
||||
|
||||
return `rgb(${darkenedR}, ${darkenedG}, ${darkenedB})`;
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './MessageHandlers';
|
||||
export * from './darkenColor';
|
||||
export * from './getRelPath';
|
||||
export * from './preserveColor';
|
||||
export * from './updateCssVariables';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { darkenColor } from './darkenColor';
|
||||
import { preserveColor } from './preserveColor';
|
||||
|
||||
export const updateCssVariables = () => {
|
||||
@@ -75,4 +76,11 @@ export const updateCssVariables = () => {
|
||||
preserveColor(buttonHoverBackground) || 'var(--vscode-button-hoverBackground)'
|
||||
);
|
||||
}
|
||||
|
||||
// Darken the background of a color
|
||||
const sideBarBg = styles.getPropertyValue('--vscode-sideBar-background');
|
||||
document.documentElement.style.setProperty(
|
||||
'--frontmatter-sideBar-background',
|
||||
darkenColor(sideBarBg, 2) || 'var(--vscode-sideBar-background)'
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user