mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 01:13:08 +02:00
Delete media
This commit is contained in:
Generated
+3
-3
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,4 +11,5 @@ export enum DashboardMessage {
|
||||
copyToClipboard = 'copyToClipboard',
|
||||
refreshMedia = 'refreshMedia',
|
||||
uploadMedia = 'uploadMedia',
|
||||
deleteMedia = 'deleteMedia',
|
||||
}
|
||||
@@ -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} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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">
|
||||
​
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const PageAtom = atom<number>({
|
||||
key: 'PageAtom',
|
||||
default: 0
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user