Skip to content

Commit

Permalink
#113 - Implemented local database for media
Browse files Browse the repository at this point in the history
  • Loading branch information
estruyf committed Sep 27, 2021
1 parent c206817 commit 4e74884
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 24 deletions.
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@
"@types/lodash.uniqby": "4.7.6",
"@types/mocha": "^5.2.6",
"@types/node": "10.17.48",
"@types/node-persist": "^3.1.2",
"@types/react": "17.0.0",
"@types/react-datepicker": "^4.1.7",
"@types/react-dom": "17.0.0",
Expand All @@ -642,6 +643,8 @@
"html-webpack-plugin": "4.5.0",
"lodash.uniqby": "4.7.0",
"mdast-util-from-markdown": "1.0.0",
"node-json-db": "^1.3.0",
"node-persist": "^3.1.0",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"react": "17.0.1",
Expand Down
31 changes: 27 additions & 4 deletions src/commands/Dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { decodeBase64Image } from '../helpers/decodeBase64Image';
import { DefaultFields } from '../constants';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { MediaLibrary } from '../helpers/MediaLibrary';


export class Dashboard {
Expand All @@ -31,6 +32,7 @@ export class Dashboard {
private static media: MediaInfo[] = [];
private static timers: { [folder: string]: any } = {};
private static _viewData: DashboardData | undefined;
private static mediaLib: MediaLibrary;

public static get viewData(): DashboardData | undefined {
return Dashboard._viewData;
Expand All @@ -40,6 +42,8 @@ export class Dashboard {
* Init the dashboard
*/
public static async init() {
this.mediaLib = MediaLibrary.getInstance();

const openOnStartup = SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART);
if (openOnStartup) {
Dashboard.open();
Expand Down Expand Up @@ -160,7 +164,7 @@ export class Dashboard {
env.clipboard.writeText(msg.data);
break;
case DashboardMessage.refreshMedia:
Dashboard.media = [];
Dashboard.resetMedia();
Dashboard.getMedia(0, msg?.data?.folder);
break;
case DashboardMessage.uploadMedia:
Expand All @@ -172,9 +176,16 @@ export class Dashboard {
case DashboardMessage.insertPreviewImage:
Dashboard.insertImage(msg?.data);
break;
case DashboardMessage.updateMediaMetadata:
Dashboard.updateMediaMetadata(msg?.data);
break;
}
});
}

public static resetMedia() {
Dashboard.media = [];
}

/**
* Insert an image into the front matter or contents
Expand All @@ -197,7 +208,7 @@ export class Dashboard {
const line = data.position.line;
const character = data.position.character;
if (line) {
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || `![](${data.image})`));
await editor?.edit(builder => builder.insert(new Position(line, character), data.snippet || `![${data.alt || data.description || ""}](${data.image})`));
}
panel.getMediaSelection();
} else {
Expand Down Expand Up @@ -295,14 +306,18 @@ export class Dashboard {
files = files.slice(page * 16, ((page + 1) * 16));
files = files.map((file) => {
try {
const metadata = Dashboard.mediaLib.get(file.fsPath);

return {
...file,
stats: statSync(file.fsPath)
stats: statSync(file.fsPath),
...metadata
};
} catch (e) {
return {...file, stats: undefined};
}
}).filter(f => f.stats !== undefined);
});
files = files.filter(f => f.stats !== undefined);

const folders = [...new Set(Dashboard.media.map((file) => {
let relFolderPath = wsFolder ? file.fsPath.substring(wsFolder.fsPath.length + 1) : file.fsPath;
Expand Down Expand Up @@ -475,6 +490,14 @@ export class Dashboard {
}
}

/**
* Update the metadata of the selected file
*/
private static async updateMediaMetadata({ file, page, folder, description = null, alt = null }: { file:string; page: number; folder: string | null; description: string | null; alt: string | null; }) {
Dashboard.mediaLib.set(file, description, alt);
Dashboard.getMedia(page || 0, folder || "");
}

/**
* Post data to the dashboard
* @param msg
Expand Down
1 change: 0 additions & 1 deletion src/commands/Folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
import { Notifications } from "../helpers/Notifications";
import { CONTEXT } from "../constants/context";
import { Settings } from "../helpers";

export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
Expand Down
1 change: 1 addition & 0 deletions src/dashboardWebView/DashboardMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export enum DashboardMessage {
uploadMedia = 'uploadMedia',
deleteMedia = 'deleteMedia',
insertPreviewImage = 'insertPreviewImage',
updateMediaMetadata = 'updateMediaMetadata',
}
2 changes: 1 addition & 1 deletion src/dashboardWebView/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useDarkMode from '../../hooks/useDarkMode';
import usePages from '../hooks/usePages';
import { WelcomeScreen } from './WelcomeScreen';
import { useRecoilValue } from 'recoil';
import { DashboardViewSelector, ViewDataAtom } from '../state';
import { DashboardViewSelector } from '../state';
import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';

Expand Down
122 changes: 113 additions & 9 deletions src/dashboardWebView/components/Media/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
import { CheckCircleIcon, ClipboardCopyIcon, CodeIcon, PencilIcon, PhotographIcon, TrashIcon } from '@heroicons/react/outline';
import { basename, dirname } from 'path';
import * as React from 'react';
import { useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { MediaInfo } from '../../../models/MediaPaths';
import { DashboardMessage } from '../../DashboardMessage';
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
import { LightboxAtom, PageSelector, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
import { Alert } from '../Modals/Alert';
import { Metadata } from '../Modals/Metadata';

export interface IItemProps {
media: MediaInfo;
Expand All @@ -17,7 +19,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const [ , setLightbox ] = useRecoilState(LightboxAtom);
const [ showAlert, setShowAlert ] = React.useState(false);
const [ showForm, setShowForm ] = React.useState(false);
const viewData = useRecoilValue(ViewDataSelector);
const [ description, setDescription ] = React.useState(media.description);
const [ alt, setAlt ] = React.useState(media.alt);
const page = useRecoilValue(PageSelector);

const parseWinPath = (path: string | undefined) => {
return path?.split(`\\`).join(`/`);
Expand Down Expand Up @@ -59,18 +65,25 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
image: parseWinPath(relPath) || "",
file: viewData?.data?.filePath,
fieldName: viewData?.data?.fieldName,
position: viewData?.data?.position || null
position: viewData?.data?.position || null,
alt: alt || "",
description: description || ""
});
};

const insertSnippet = () => {
const relPath = getRelPath();
let snippet = settings?.mediaSnippet.join("\n");
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
snippet = snippet?.replace("{alt}", alt || "");
snippet = snippet?.replace("{description}", description || "");

Messenger.send(DashboardMessage.insertPreviewImage, {
image: parseWinPath(relPath) || "",
file: viewData?.data?.filePath,
fieldName: viewData?.data?.fieldName,
position: viewData?.data?.position || null,
snippet: settings?.mediaSnippet.join("\n").replace("{mediaUrl}", parseWinPath(relPath) || "")
snippet
});
};

Expand Down Expand Up @@ -100,6 +113,33 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
setLightbox(media.vsPath || "");
};

const updateMetadata = () => {
setShowForm(true);
};

const submitMetadata = () => {
Messenger.send(DashboardMessage.updateMediaMetadata, {
file: media.fsPath,
description,
alt,
folder: selectedFolder,
page
});
setShowForm(false);
};

useEffect(() => {
if (media.alt !== description) {
setAlt(media.alt);
}
}, [media.alt]);

useEffect(() => {
if (media.description !== description) {
setDescription(media.description);
}
}, [media.description]);

return (
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
Expand All @@ -112,7 +152,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</div>
</button>
<div className={`relative py-4 pl-4 pr-10`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-2`}>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
{
viewData?.data?.filePath ? (
<>
Expand All @@ -137,6 +177,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</>
) : (
<>
<button title={`Edit metadata`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={updateMetadata}>
<PencilIcon className={`h-5 w-5`} />
<span className={`sr-only`}>Edit metadata</span>
</button>
<button title={`Copy media path`}
className={`hover:text-teal-900 focus:outline-none`}
onClick={copyToClipboard}>
Expand All @@ -156,19 +202,77 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
<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()}
{
media.description && (
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-2`}>Description:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.description}</span>
</p>
)
}
{
media.alt && (
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-2`}>Alt:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{media.alt}</span>
</p>
)
}
<p className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-2`}>Folder:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{getFolder()}</span>
</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 className="mt-2 text-xs dark:text-whisper-900 font-medium pointer-events-none flex flex-col items-start">
<b className={`mr-1`}>Size:</b>
<span className={`block mt-1 dark:text-whisper-500 text-xs`}>{calculateSize()}</span>
</p>
)
}
</div>
</li>

{
showForm && (
<Metadata
title={`Set metadata for: ${basename(parseWinPath(media.fsPath) || "")}`}
description={`Please specify the metadata you want to set for the file.`}
okBtnText={`Save`}
cancelBtnText={`Cancel`}
dismiss={() => setShowForm(false)}
trigger={submitMetadata}>
<div className="flex flex-col space-y-2">
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Description
</label>
<div className="mt-1">
<textarea
rows={3}
className="py-1 px-2 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none w-full"
value={description || ''}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700 dark:text-whisper-900">
Alt tag value
</label>
<div className="mt-1">
<input
className="py-1 px-2 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none w-full"
value={alt || ''}
onChange={(e) => setAlt(e.target.value)}
/>
</div>
</div>
</div>
</Metadata>
)
}

{
showAlert && (
<Alert
Expand Down
Loading

0 comments on commit 4e74884

Please sign in to comment.