#669 - video preview + caption field

This commit is contained in:
Elio Struyf
2023-09-18 11:21:30 +02:00
parent 08b8fa6fdb
commit ac3b4a012b
5 changed files with 72 additions and 29 deletions
+1
View File
@@ -12,6 +12,7 @@
- [#659](https://github.com/estruyf/vscode-front-matter/issues/659): Implement a filter for the taxonomy dashboard
- [#662](https://github.com/estruyf/vscode-front-matter/issues/662): Always show the `all articles` tab with the page counter
- [#663](https://github.com/estruyf/vscode-front-matter/issues/663): Make card tags clickable to filter the view
- [#669](https://github.com/estruyf/vscode-front-matter/issues/669): Add the video preview to the details panel + caption field
### ⚡️ Optimizations
@@ -23,6 +23,7 @@ export interface IDetailsSlideOverProps {
media: MediaInfo;
showForm: boolean;
isImageFile: boolean;
isVideoFile: boolean;
onEdit: () => void;
onEditClose: () => void;
onDismiss: () => void;
@@ -38,7 +39,8 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
onEdit,
onEditClose,
onDismiss,
isImageFile
isImageFile,
isVideoFile
}: React.PropsWithChildren<IDetailsSlideOverProps>) => {
const [filename, setFilename] = React.useState<string>(media.filename);
const [caption, setCaption] = React.useState<string | undefined>(media.caption);
@@ -136,9 +138,19 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
<div className="relative mt-6 flex-1 px-4 sm:px-6">
<div className="absolute inset-0 px-4 sm:px-6 space-y-8">
<div>
{isImageFile && (
{(isImageFile || isVideoFile) && (
<div className={`block w-full aspect-w-10 aspect-h-7 overflow-hidden border rounded border-[var(--frontmatter-border)] bg-[var(--vscode-editor-background)]`}>
<img src={imgSrc} alt={media.filename} className="object-cover max-h-[300px] mx-auto" />
{
isImageFile && (
<img src={imgSrc} alt={media.filename} className="object-cover max-h-[300px] mx-auto" />
)
}
{
isVideoFile && (
<video src={media.vsPath} className="mx-auto object-cover" controls muted />
)
}
</div>
)}
<div className="mt-4 flex items-start justify-between">
@@ -211,25 +223,25 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
</div>
</div>
{(isImageFile || isVideoFile) && (
<div>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
</label>
<div className="mt-1">
<DetailsInput value={caption || ""} onChange={(e) => setCaption(e.target.value)} isTextArea />
</div>
</div>
)}
{isImageFile && (
<>
<div>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
</label>
<div className="mt-1">
<DetailsInput value={caption || ""} onChange={(e) => setCaption(e.target.value)} isTextArea />
</div>
<div>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
</label>
<div className="mt-1">
<DetailsInput value={alt || ""} onChange={(e) => setAlt(e.target.value)} isTextArea />
</div>
<div>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
</label>
<div className="mt-1">
<DetailsInput value={alt || ""} onChange={(e) => setAlt(e.target.value)} isTextArea />
</div>
</div>
</>
</div>
)}
</div>
@@ -701,6 +701,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
media={media}
showForm={showForm}
isImageFile={isImageFile}
isVideoFile={isVideoFile}
onEdit={() => setShowForm(true)}
onEditClose={() => setShowForm(false)}
onDismiss={() => {
+2
View File
@@ -350,6 +350,8 @@ export class MediaHelpers {
try {
await unlinkAsync(file);
MediaLibrary.getInstance().remove(file);
MediaHelpers.media = [];
return true;
} catch (err: any) {
+36 -9
View File
@@ -18,6 +18,7 @@ interface MediaRecord {
export class MediaLibrary {
private db: JsonDB | undefined;
private renameFilesListener: Disposable | undefined;
private removeFilesListener: Disposable | undefined;
private static instance: MediaLibrary;
private constructor() {
@@ -74,20 +75,26 @@ export class MediaLibrary {
this.renameFilesListener = workspace.onDidRenameFiles((e) => {
e.files.forEach(async (f) => {
const path = f.oldUri.path.toLowerCase();
const mimeType = lookup(path);
// Check if file is an image
if (
mimeType &&
(mimeType.startsWith('image/') ||
mimeType.startsWith('video/') ||
mimeType.startsWith('audio/') ||
mimeType.startsWith('application/pdf'))
) {
if (MediaLibrary.isMediaFile(path)) {
await this.rename(f.oldUri.fsPath, f.newUri.fsPath);
MediaHelpers.resetMedia();
}
});
});
if (this.removeFilesListener) {
this.removeFilesListener.dispose();
}
this.removeFilesListener = workspace.onDidDeleteFiles((e) => {
e.files.forEach(async (f) => {
const path = f.path.toLowerCase();
if (MediaLibrary.isMediaFile(path)) {
this.remove(f.fsPath);
MediaHelpers.resetMedia();
}
});
});
}
public static getInstance(): MediaLibrary {
@@ -138,6 +145,11 @@ export class MediaLibrary {
}
}
public async remove(path: string): Promise<void> {
const fileId = this.parsePath(path);
await this.db?.delete(fileId);
}
public async updateFilename(filePath: string, filename: string) {
const name = basename(filePath);
@@ -167,4 +179,19 @@ export class MediaLibrary {
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
return absPath.toLowerCase();
}
private static isMediaFile(path: string) {
const mimeType = lookup(path);
// Check if file is an image
if (
mimeType &&
(mimeType.startsWith('image/') ||
mimeType.startsWith('video/') ||
mimeType.startsWith('audio/') ||
mimeType.startsWith('application/pdf'))
) {
return true;
}
return false;
}
}