From 4e7488414d485a4d83437315251af73e5886ceee Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 27 Sep 2021 16:22:23 +0200 Subject: [PATCH] #113 - Implemented local database for media --- package-lock.json | 32 +++++ package.json | 3 + src/commands/Dashboard.ts | 31 ++++- src/commands/Folders.ts | 1 - src/dashboardWebView/DashboardMessage.ts | 1 + src/dashboardWebView/components/Dashboard.tsx | 2 +- .../components/Media/Item.tsx | 122 ++++++++++++++++-- .../components/Media/Pagination.tsx | 4 +- .../components/Modals/Metadata.tsx | 90 +++++++++++++ .../state/selectors/PageSelector.ts | 9 ++ src/dashboardWebView/state/selectors/index.ts | 1 + src/extension.ts | 6 - src/helpers/FilesHelper.ts | 1 - src/helpers/MediaLibrary.ts | 76 +++++++++++ src/models/MediaPaths.ts | 2 + 15 files changed, 357 insertions(+), 24 deletions(-) create mode 100644 src/dashboardWebView/components/Modals/Metadata.tsx create mode 100644 src/dashboardWebView/state/selectors/PageSelector.ts create mode 100644 src/helpers/MediaLibrary.ts diff --git a/package-lock.json b/package-lock.json index dd5761c0..a6e05c2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -359,6 +359,15 @@ "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", "dev": true }, + "@types/node-persist": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/node-persist/-/node-persist-3.1.2.tgz", + "integrity": "sha512-aLFUB1951wOfR+tZ4f3TudLPblo9+PfnduMh6feuOTijD0Q6YkMdqPXSgQIjI23FGmCuoZaIZ9x6cvEG/TdiSg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -4106,6 +4115,23 @@ "lodash": "^4.17.21" } }, + "node-json-db": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-1.3.0.tgz", + "integrity": "sha512-3IK9KuqfKdK12zFKtzmnD6Y5J9qL0TY0gnnZ5XKpzdhCP019zOxPxGCaH6cmIqiho2ymFgcTMKQeJvYkbBFraQ==", + "dev": true, + "requires": { + "mkdirp": "~1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -4145,6 +4171,12 @@ } } }, + "node-persist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.0.tgz", + "integrity": "sha512-/j+fd/u71wNgKf3V2bx4tnDm+3GvLnlCuvf2MXbJ3wern+67IAb6zN9Leu1tCWPlPNZ+v1hLSibVukkPK2HqJw==", + "dev": true + }, "node-releases": { "version": "1.1.75", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", diff --git a/package.json b/package.json index 42797f1a..d5d1aa99 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 7de12e45..b85580c0 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -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 { @@ -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; @@ -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(); @@ -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: @@ -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 @@ -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 { @@ -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; @@ -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 diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 61f7ca15..d7079b16 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -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]]`; diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 1afc8ac0..71b24f08 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -14,4 +14,5 @@ export enum DashboardMessage { uploadMedia = 'uploadMedia', deleteMedia = 'deleteMedia', insertPreviewImage = 'insertPreviewImage', + updateMediaMetadata = 'updateMediaMetadata', } \ No newline at end of file diff --git a/src/dashboardWebView/components/Dashboard.tsx b/src/dashboardWebView/components/Dashboard.tsx index f366cdd5..c48071b3 100644 --- a/src/dashboardWebView/components/Dashboard.tsx +++ b/src/dashboardWebView/components/Dashboard.tsx @@ -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'; diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index ef2be18b..c9a010eb 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -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; @@ -17,7 +19,11 @@ export const Item: React.FunctionComponent = ({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(`/`); @@ -59,18 +65,25 @@ export const Item: React.FunctionComponent = ({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 }); }; @@ -100,6 +113,33 @@ export const Item: React.FunctionComponent = ({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 ( <>
  • @@ -112,7 +152,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi
    -
    +
    { viewData?.data?.filePath ? ( <> @@ -137,6 +177,12 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi ) : ( <> +
  • + { + showForm && ( + setShowForm(false)} + trigger={submitMetadata}> +
    +
    + +
    +