Delete media

This commit is contained in:
Elio Struyf
2021-09-10 14:01:46 +02:00
parent 3f237386b2
commit 840e6fc6fd
8 changed files with 188 additions and 45 deletions
+3 -3
View File
@@ -219,9 +219,9 @@
}
},
"@headlessui/react": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.0.tgz",
"integrity": "sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
"dev": true
},
"@heroicons/react": {
+1 -1
View File
@@ -397,7 +397,7 @@
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
"@estruyf/vscode": "0.0.2",
"@headlessui/react": "1.4.0",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "1.0.4",
"@iarna/toml": "2.2.3",
"@tailwindcss/forms": "^0.3.3",
+24 -2
View File
@@ -1,7 +1,7 @@
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART } from './../constants/settings';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { dirname, extname, join } from "path";
import { existsSync, statSync, writeFileSync } from "fs";
import { basename, dirname, extname, join } from "path";
import { existsSync, statSync, unlinkSync, writeFileSync } from "fs";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env } from "vscode";
import { SettingsHelper } from '../helpers';
import { TaxonomyType } from '../models';
@@ -147,6 +147,9 @@ export class Dashboard {
case DashboardMessage.uploadMedia:
Dashboard.saveFile(msg?.data);
break;
case DashboardMessage.deleteMedia:
Dashboard.deleteFile(msg?.data);
break;
}
});
}
@@ -403,6 +406,25 @@ export class Dashboard {
}
}
/**
* Delete the selected file
* @param fileData
*/
private static async deleteFile({ file, page, folder }: { file: string; page: number; folder: string | null; }) {
if (!file) {
return;
}
try {
unlinkSync(file);
Dashboard.media = [];
Dashboard.getMedia(page || 0, folder || "");
} catch(err) {
Notifications.error(`Something went wrong deleting ${basename(file)}`);
}
}
/**
* Post data to the dashboard
* @param msg
+1
View File
@@ -11,4 +11,5 @@ export enum DashboardMessage {
copyToClipboard = 'copyToClipboard',
refreshMedia = 'refreshMedia',
uploadMedia = 'uploadMedia',
deleteMedia = 'deleteMedia',
}
+63 -39
View File
@@ -5,7 +5,8 @@ import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { MediaInfo } from '../../../models/MediaPaths';
import { DashboardMessage } from '../../DashboardMessage';
import { LightboxAtom, SettingsSelector } from '../../state';
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector } from '../../state';
import { Alert } from '../Modals/Alert';
export interface IItemProps {
media: MediaInfo;
@@ -13,7 +14,9 @@ export interface IItemProps {
export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWithChildren<IItemProps>) => {
const settings = useRecoilValue(SettingsSelector);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const [ , setLightbox ] = useRecoilState(LightboxAtom);
const [ showAlert, setShowAlert ] = React.useState(false);
const parseWinPath = (path: string | undefined) => {
return path?.split(`\\`).join(`/`);
@@ -46,7 +49,14 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
};
const deleteMedia = () => {
setShowAlert(true);
};
const confirmDeletion = () => {
Messenger.send(DashboardMessage.deleteMedia, {
file: media.fsPath,
folder: selectedFolder
});
};
const calculateSize = () => {
@@ -65,44 +75,58 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
};
return (
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
</div>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
</div>
</button>
<div className={`relative py-4 pl-4 pr-10`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
<button title={`Copy media path`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={copyToClipboard}>
<ClipboardCopyIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Copy media path</span>
</button>
<button title={`Delete media`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={deleteMedia}>
<TrashIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Delete media</span>
</button>
</div>
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
{basename(parseWinPath(media.fsPath) || "")}
</p>
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
<b className={`mr-2`}>Folder:</b> {getFolder()}
</p>
{
media?.stats?.size && (
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
<b className={`mr-1`}>Size:</b> {calculateSize()}
</p>
)
}
</div>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover" />
</div>
</button>
<div className={`relative py-4 pl-4 pr-10`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
<button title={`Copy media path`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={copyToClipboard}>
<ClipboardCopyIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Copy media path</span>
</button>
<button title={`Delete media`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={deleteMedia}>
<TrashIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Delete media</span>
</button>
</div>
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
{basename(parseWinPath(media.fsPath) || "")}
</p>
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
<b className={`mr-2`}>Folder:</b> {getFolder()}
</p>
{
media?.stats?.size && (
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
<b className={`mr-1`}>Size:</b> {calculateSize()}
</p>
)
}
</div>
</li>
</li>
{
showAlert && (
<Alert
title={`Delete: ${basename(parseWinPath(media.fsPath) || "")}`}
description={`Are you sure you want to delete the file from the ${getFolder()} folder?`}
okBtnText={`Delete`}
cancelBtnText={`Cancel`}
dismiss={() => setShowAlert(false)}
trigger={confirmDeletion} />
)
}
</>
);
};
+89
View File
@@ -0,0 +1,89 @@
import { Dialog, Transition, } from '@headlessui/react';
import { ExclamationIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { Fragment, useRef } from 'react';
export interface IAlertProps {
title: string;
description: string;
okBtnText: string;
cancelBtnText: string;
dismiss: () => void;
trigger: () => void;
}
export const Alert: React.FunctionComponent<IAlertProps> = ({title, description, cancelBtnText, okBtnText, dismiss, trigger}: React.PropsWithChildren<IAlertProps>) => {
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={true} as={Fragment}>
<Dialog className="fixed z-10 inset-0 overflow-y-auto" initialFocus={cancelButtonRef} onClose={() => dismiss()}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-vulcan-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white dark:bg-vulcan-500 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 border-whisper-900">
<div className="sm:flex sm:items-start">
<div className="mt-3 mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full sm:mx-0 sm:h-10 sm:w-10 bg-gray-50 dark:bg-vulcan-400">
<ExclamationIcon className="h-6 w-6 text-red-500 dark:text-red-50" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-vulcan-300 dark:text-whisper-900">
{title}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-vulcan-500 dark:text-whisper-500">
{description}
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 dark:hover:bg-red-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => trigger()}
>
{okBtnText}
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
onClick={() => dismiss()}
ref={cancelButtonRef}
>
{cancelBtnText}
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
};
+6
View File
@@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const PageAtom = atom<number>({
key: 'PageAtom',
default: 0
});
+1
View File
@@ -6,6 +6,7 @@ export * from './LightboxAtom';
export * from './LoadingAtom';
export * from './MediaFoldersAtom';
export * from './MediaTotalAtom';
export * from './PageAtom';
export * from './SearchAtom';
export * from './SelectedMediaFolderAtom';
export * from './SettingsAtom';