Skip to content

Commit

Permalink
Merge pull request #893 from estruyf/issue/892
Browse files Browse the repository at this point in the history
Add media folder actions and localization updates #892
  • Loading branch information
estruyf authored Nov 29, 2024
2 parents b48e34e + 3241849 commit 7bfc724
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [#705](https://github.com/estruyf/vscode-front-matter/issues/705): UX improvements for the panel view
- [#887](https://github.com/estruyf/vscode-front-matter/issues/887): Added new `frontMatter.global.timezone` setting, by default it is set to `UTC` for date formatting
- [#888](https://github.com/estruyf/vscode-front-matter/issues/888): Added the ability to prompt GitHub Copilot from a custom script/action
- [#892](https://github.com/estruyf/vscode-front-matter/issues/892): Added media folder common actions

### 🐞 Fixes

Expand Down
4 changes: 4 additions & 0 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@

"dashboard.media.folderItem.contentDirectory": "Content directory",
"dashboard.media.folderItem.publicDirectory": "Public directory",
"dashboard.media.folderItem.deleteDescription": "Are you sure you want to delete the folder ({0})?",

"dashboard.media.item.buttom.insert.image": "Insert image",
"dashboard.media.item.buttom.insert.snippet": "Insert snippet",
Expand Down Expand Up @@ -775,6 +776,9 @@
"listeners.dashboard.dashboardListener.pinItem.coundNotPin.error": "Could not pin item.",
"listeners.dashboard.dashboardListener.pinItem.coundNotUnPin.error": "Could not unpin item.",

"listeners.dashboard.mediaListeners.deleteMediaFolder.progress.title": "Deleting folder...",
"listeners.dashboard.mediaListeners.updateMediaFolder.progress.title": "Updating folder...",

"listeners.dashboard.settingsListener.triggerTemplate.notification": "Template files copied.",
"listeners.dashboard.settingsListener.triggerTemplate.progress.title": "Downloading and initializing the template...",
"listeners.dashboard.settingsListener.triggerTemplate.download.error": "Failed to download the template.",
Expand Down
2 changes: 2 additions & 0 deletions src/dashboardWebView/DashboardMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export enum DashboardMessage {
insertMedia = 'insertMedia',
updateMediaMetadata = 'updateMediaMetadata',
createMediaFolder = 'createMediaFolder',
updateMediaFolder = 'updateMediaFolder',
deleteMediaFolder = 'deleteMediaFolder',
insertFile = 'insertFile',
createHexoAssetFolder = 'createHexoAssetFolder',
getUnmappedMedia = 'getUnmappedMedia',
Expand Down
99 changes: 75 additions & 24 deletions src/dashboardWebView/components/Media/FolderItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { FolderIcon } from '@heroicons/react/24/solid';
import { FolderIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/solid';
import { basename, join } from 'path';
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { LocalizationKey, localize } from '../../../localization';
import useMediaFolder from '../../hooks/useMediaFolder';
import { QuickAction } from '../Menu';
import { messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { useState } from 'react';
import { Alert } from '../Modals/Alert';
import { parseWinPath } from '../../../helpers/parseWinPath';

export interface IFolderItemProps {
folder: string;
Expand All @@ -17,6 +22,7 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
staticFolder
}: React.PropsWithChildren<IFolderItemProps>) => {
const { updateFolder } = useMediaFolder();
const [showAlert, setShowAlert] = useState(false);

const relFolderPath = wsFolder ? folder.replace(wsFolder, '') : folder;

Expand All @@ -25,28 +31,73 @@ export const FolderItem: React.FunctionComponent<IFolderItemProps> = ({
[relFolderPath, staticFolder]
);

const updateFolderName = React.useCallback(() => {
messageHandler.send(DashboardMessage.updateMediaFolder, { folder, wsFolder, staticFolder })
}, []);

const onDelete = React.useCallback(() => {
setShowAlert(true);
}, []);

const confirmDeletion = React.useCallback(() => {
messageHandler.send(DashboardMessage.deleteMediaFolder, { folder });
setShowAlert(false);
}, [folder]);

return (
<li
className={`group relative hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-editor-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)]`}
>
<button
title={isContentFolder ? l10n.t(LocalizationKey.dashboardMediaFolderItemContentDirectory) : l10n.t(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
className={`p-4 w-full flex flex-row items-center h-full`}
onClick={() => updateFolder(folder)}
<>
<li
className={`flex flex-col group relative text-[var(--vscode-sideBarTitle-foreground)] hover:text-[var(--vscode-list-activeSelectionForeground)] shadow-md hover:shadow-xl dark:shadow-none bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] border border-[var(--frontmatter-border)] rounded`}
>
<div className="relative mr-4">
<FolderIcon className={`h-12 w-12`} />
{isContentFolder && (
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--frontmatter-text)]`}>
C
</span>
)}
</div>

<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
{basename(relFolderPath)}
</p>
</button>
</li>
<button
title={isContentFolder ? localize(LocalizationKey.dashboardMediaFolderItemContentDirectory) : localize(LocalizationKey.dashboardMediaFolderItemPublicDirectory)}
className={`p-4 w-full flex flex-row items-center h-full`}
onClick={() => updateFolder(folder)}
>
<div className="relative mr-4">
<FolderIcon className={`h-12 w-12`} />
{isContentFolder && (
<span className={`font-extrabold absolute bottom-3 left-1/2 transform -translate-x-1/2 text-[var(--frontmatter-text)]`}>
C
</span>
)}
</div>

<p className="text-sm font-bold pointer-events-none flex items-center text-left overflow-hidden break-words">
{basename(relFolderPath)}
</p>
</button>

{!isContentFolder && (
<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={localize(LocalizationKey.commonEdit)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={updateFolderName}>
<PencilIcon className={`w-4 h-4`} aria-hidden="true" />
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
</QuickAction>

<QuickAction
title={localize(LocalizationKey.dashboardMediaItemQuickActionDelete)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
</QuickAction>
</div>
)}
</li>

{showAlert && (
<Alert
title={`${localize(LocalizationKey.commonDelete)}: ${basename(parseWinPath(folder) || '')}`}
description={localize(LocalizationKey.dashboardMediaFolderItemDeleteDescription, folder)}
okBtnText={localize(LocalizationKey.commonDelete)}
cancelBtnText={localize(LocalizationKey.commonCancel)}
dismiss={() => setShowAlert(false)}
trigger={confirmDeletion}
/>
)}
</>
);
};
21 changes: 10 additions & 11 deletions src/dashboardWebView/components/Media/FooterActions.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as l10n from '@vscode/l10n';
import { QuickAction } from '../Menu';
import { LocalizationKey } from '../../../localization';
import { LocalizationKey, localize } from '../../../localization';
import { ClipboardIcon, CodeBracketIcon, EyeIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import { useRecoilState } from 'recoil';
import { SelectedItemActionAtom } from '../../state';
Expand Down Expand Up @@ -36,34 +35,34 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
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)}
title={localize(LocalizationKey.dashboardMediaItemMenuItemView)}
className={`text-[var(--frontmatter-secondary-text)]`}
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>
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemView)}</span>
</QuickAction>

<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
title={localize(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}
className={`text-[var(--frontmatter-secondary-text)]`}
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>
<span className='sr-only'>{localize(LocalizationKey.dashboardMediaItemMenuItemEditMetadata)}</span>
</QuickAction>

{viewData?.filePath ? (
<>
<QuickAction
title={
viewData.metadataInsert && viewData.fieldName
? l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
: l10n.t(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
? localize(LocalizationKey.dashboardMediaItemQuickActionInsertField, viewData.fieldName)
: localize(LocalizationKey.dashboardMediaItemQuickActionInsertMarkdown)
}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={insertIntoArticle}
Expand All @@ -73,7 +72,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({

{viewData?.position && snippets.length > 0 && (
<QuickAction
title={l10n.t(LocalizationKey.commonInsertSnippet)}
title={localize(LocalizationKey.commonInsertSnippet)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={insertSnippet}>
<CodeBracketIcon className={`w-4 h-4`} aria-hidden="true" />
Expand All @@ -85,7 +84,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
{
relPath && (
<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
title={localize(LocalizationKey.dashboardMediaItemQuickActionCopyPath)}
className={`text-[var(--frontmatter-secondary-text)]`}
onClick={() => copyToClipboard(parseWinPath(relPath) || '')}>
<ClipboardIcon className={`w-4 h-4`} aria-hidden="true" />
Expand All @@ -101,7 +100,7 @@ export const FooterActions: React.FunctionComponent<IFooterActionsProps> = ({
showTrigger />

<QuickAction
title={l10n.t(LocalizationKey.dashboardMediaItemQuickActionDelete)}
title={localize(LocalizationKey.dashboardMediaItemQuickActionDelete)}
className={`text-[var(--frontmatter-secondary-text)] hover:text-[var(--vscode-statusBarItem-errorBackground)]`}
onClick={onDelete}>
<TrashIcon className={`w-4 h-4`} aria-hidden="true" />
Expand Down
18 changes: 13 additions & 5 deletions src/dashboardWebView/components/Media/Media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PagedItems,
SelectedMediaFolderAtom,
SettingsSelector,
SortingAtom,
ViewDataSelector
} from '../../state';
import { Spinner } from '../Common/Spinner';
Expand All @@ -30,18 +31,18 @@ import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { MediaItemPanel } from './MediaItemPanel';
import { FilesProvider } from '../../providers/FilesProvider';
import { SortOption } from '../../constants/SortOption';

export interface IMediaProps { }

export const Media: React.FunctionComponent<IMediaProps> = (
_: React.PropsWithChildren<IMediaProps>
) => {
export const Media: React.FunctionComponent<IMediaProps> = () => {
const { media } = useMedia();
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const selectedFolder = useRecoilValue(SelectedMediaFolderAtom);
const folders = useRecoilValue(MediaFoldersAtom);
const loading = useRecoilValue(LoadingAtom);
const crntSorting = useRecoilValue(SortingAtom);
const [, setPagedItems] = useRecoilState(PagedItems);

const currentStaticFolder = useMemo(() => {
Expand Down Expand Up @@ -85,11 +86,18 @@ export const Media: React.FunctionComponent<IMediaProps> = (
currentStaticFolder &&
settings?.staticFolder !== STATIC_FOLDER_PLACEHOLDER.hexo.placeholder
) {
return folders.filter((f) => parseWinPath(f).includes(currentStaticFolder));
const allFolders = folders.filter((f) => parseWinPath(f).includes(currentStaticFolder));
if (crntSorting && crntSorting.id === SortOption.FileNameAsc) {
return allFolders.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
} else if (crntSorting && crntSorting.id === SortOption.FileNameDesc) {
return allFolders.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
} else {
return allFolders;
}
}

return undefined;
}, [folders, viewData, currentStaticFolder, settings?.staticFolder]);
}, [folders, viewData, currentStaticFolder, settings?.staticFolder, crntSorting]);

const allMedia = useMemo(() => {
let mediaFiles: MediaInfo[] = Object.assign([], media);
Expand Down
9 changes: 9 additions & 0 deletions src/helpers/MediaLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ export class MediaLibrary {
}
}

public async getAllByPath(path: string) {
try {
const data = await this.db?.getData(path);
return data;
} catch {
return undefined;
}
}

public set(id: string, metadata: any): void {
const fileId = this.parsePath(id);
this.db?.push(fileId, metadata, true);
Expand Down
Loading

0 comments on commit 7bfc724

Please sign in to comment.