#623 - Fix update metadata on move

This commit is contained in:
Elio Struyf
2023-09-14 17:31:57 +02:00
parent c952cf4057
commit 180ea7880b
24 changed files with 279 additions and 126 deletions
+2
View File
@@ -37,6 +37,8 @@ export enum DashboardMessage {
createMediaFolder = 'createMediaFolder',
insertFile = 'insertFile',
createHexoAssetFolder = 'createHexoAssetFolder',
getUnmappedMedia = 'getUnmappedMedia',
remapMediaMetadata = 'remapMediaMetadata',
// Data dashboard
getDataEntries = 'getDataEntries',
@@ -1,7 +1,7 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { Page } from '../../models';
import { SettingsSelector } from '../../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { NavigationType, Page } from '../../models';
import { DashboardViewAtom, SettingsSelector } from '../../state';
import { Overview } from './Overview';
import { Spinner } from '../Common/Spinner';
import { SponsorMsg } from '../Layout/SponsorMsg';
@@ -23,10 +23,13 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({
}: React.PropsWithChildren<IContentsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const { pageItems } = usePages(pages);
const [, setView] = useRecoilState(DashboardViewAtom);
const pageFolders = [...new Set(pageItems.map((page) => page.fmFolder))];
useEffect(() => {
setView(NavigationType.Contents);
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewContentsView
});
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Header } from '../Header';
import { useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { useRecoilState, useRecoilValue } from 'recoil';
import { DashboardViewAtom, SettingsSelector } from '../../state';
import { DataForm } from './DataForm';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { DataFile } from '../../../models/DataFile';
@@ -24,6 +24,7 @@ import { NavigationItem } from '../Layout';
import useThemeColors from '../../hooks/useThemeColors';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { NavigationType } from '../../models';
export interface IDataViewProps { }
@@ -35,6 +36,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
const [dataEntries, setDataEntries] = useState<any | any[] | null>(null);
const settings = useRecoilValue(SettingsSelector);
const { getColors } = useThemeColors();
const [, setView] = useRecoilState(DashboardViewAtom);
const setSchema = (dataFile: DataFile) => {
setSelectedData(dataFile);
@@ -135,6 +137,7 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (
}, [selectedData, , dataEntries, selectedIndex]);
useEffect(() => {
setView(NavigationType.Data);
Messenger.listen(messageListener);
Messenger.send(DashboardMessage.sendTelemetry, {
@@ -5,12 +5,11 @@ import { basename } from 'path';
import * as React from 'react';
import { Fragment, useCallback, useMemo } from 'react';
import { DateHelper } from '../../../helpers/DateHelper';
import { MediaInfo } from '../../../models';
import { Messenger } from '@estruyf/vscode/dist/client';
import { MediaInfo, UnmappedMedia } from '../../../models';
import { Messenger, messageHandler } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { useRecoilValue } from 'recoil';
import { PageSelector, SelectedMediaFolderSelector } from '../../state';
import useThemeColors from '../../hooks/useThemeColors';
import { DetailsItem } from './DetailsItem';
import { DetailsInput } from './DetailsInput';
import * as l10n from '@vscode/l10n';
@@ -44,10 +43,10 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
const [filename, setFilename] = React.useState<string>(media.filename);
const [caption, setCaption] = React.useState<string | undefined>(media.caption);
const [title, setTitle] = React.useState<string | undefined>(media.title);
const [unmapped, setUnmapped] = React.useState<UnmappedMedia[]>([]);
const [alt, setAlt] = React.useState(media.alt);
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const page = useRecoilValue(PageSelector);
const { getColors } = useThemeColors();
const createdDate = useMemo(() => DateHelper.tryParse(media.ctime), [media]);
const modifiedDate = useMemo(() => DateHelper.tryParse(media.mtime), [media]);
@@ -70,6 +69,17 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
onEditClose();
}, [media, filename, caption, alt, title, selectedFolder, page]);
const remapMetadata = useCallback((item: UnmappedMedia) => {
Messenger.send(DashboardMessage.remapMediaMetadata, {
file: media.fsPath,
unmappedItem: item,
folder: selectedFolder,
page
});
onEditClose();
}, [media, filename, caption, alt, title, selectedFolder, page]);
React.useEffect(() => {
setTitle(media.title);
setAlt(media.alt);
@@ -77,15 +87,21 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
setFilename(media.filename);
}, [media]);
React.useEffect(() => {
if (showForm) {
messageHandler.request<UnmappedMedia[]>(DashboardMessage.getUnmappedMedia, filename).then((result) => {
setUnmapped(result);
});
} else {
setUnmapped([]);
}
}, [showForm, filename]);
return (
<Transition.Root show={true} as={Fragment}>
<Dialog onClose={onDismiss} as={'div' as any} className="fixed inset-0 overflow-hidden z-50">
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className={`absolute inset-0 transition-opacity ${getColors(
'bg-vulcan-500 bg-opacity-75',
'bg-[var(--vscode-editor-background)] opacity-75'
)
}`} />
<Dialog.Overlay className={`absolute inset-0 transition-opacity bg-[var(--vscode-editor-background)] opacity-75`} />
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
@@ -98,28 +114,16 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
leaveTo="translate-x-full"
>
<div className="pointer-events-auto w-screen max-w-md">
<div className={`flex h-full flex-col overflow-y-scroll border-l py-6 shadow-xl ${getColors(
`bg-white dark:bg-vulcan-400 border-whisper-900 dark:border-vulcan-50`,
`bg-[var(--vscode-sideBar-background)] border-[var(--frontmatter-border)]`
)
}`}>
<div className={`flex h-full flex-col overflow-y-scroll border-l py-6 shadow-xl bg-[var(--vscode-sideBar-background)] border-[var(--frontmatter-border)]`}>
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<Dialog.Title className={`text-lg font-medium ${getColors(
'text-vulcan-300 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<Dialog.Title className={`text-lg font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaDialogTitle)}
</Dialog.Title>
<div className="ml-3 flex h-7 items-center">
<button
type="button"
className={`focus:outline-none ${getColors(
'text-vulcan-300 dark:text-whisper-900 hover:text-vulcan-500 dark:hover:text-whisper-500',
'text-[var(--vscode-titleBar-inactiveForeground)] hover:text-[var(--vscode-titleBar-activeForeground)]'
)
}`}
className={`focus:outline-none text-[var(--vscode-titleBar-inactiveForeground)] hover:text-[var(--vscode-titleBar-activeForeground)]`}
onClick={onDismiss}
>
<span className="sr-only">{l10n.t(LocalizationKey.dashboardMediaPanelClose)}</span>
@@ -133,28 +137,16 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
<div className="absolute inset-0 px-4 sm:px-6 space-y-8">
<div>
{isImageFile && (
<div className={`block w-full aspect-w-10 aspect-h-7 overflow-hidden border rounded ${getColors(
'border-gray-200 dark:border-vulcan-200',
'border-[var(--frontmatter-border)] bg-[var(--vscode-editor-background)]'
)
}`}>
<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" />
</div>
)}
<div className="mt-4 flex items-start justify-between">
<div>
<h2 className={`text-lg font-medium ${getColors(
'text-vulcan-300 dark:text-whisper-500',
'text-[var(--vscode-foreground)]'
)
}`}>
<h2 className={`text-lg font-medium text-[var(--vscode-foreground)]`}>
{media.filename}
</h2>
<p className={`text-sm font-medium ${getColors(
'text-vulcan-100 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<p className={`text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{size}
</p>
</div>
@@ -165,44 +157,53 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{/* EDIT METADATA FORM */}
{showForm && (
<>
<h3 className={`text-base ${getColors(
'text-vulcan-300 dark:text-whisper-500',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<h3 className={`text-base text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelTitle)}
</h3>
<p className={`text-sm font-medium ${getColors(
'text-vulcan-100 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
{
unmapped && unmapped.length > 0 && (
<div className="flex flex-col py-3 space-y-3">
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
{l10n.t(LocalizationKey.dashboardMediaDetailsSlideOverUnmappedDescription)}
</p>
<ul className='pl-4'>
{
unmapped.map((item) => (
<li className='list-disc'>
<button
key={item.file}
className='hover:text-[var(--frontmatter-link-hover)]'
onClick={() => remapMetadata(item)}>
{item.file}{item.metadata.title ? ` (${item.metadata.title})` : ''}
</button>
</li>
))
}
</ul>
</div>
)
}
<p className={`text-sm my-3 font-medium text-[var(--vscode-editor-foreground)] opacity-90`}>
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelDescription)}
</p>
<div className="flex flex-col py-3 space-y-3">
<div>
<label className={`block text-sm font-medium ${getColors(
'text-gray-700 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)}
</label>
<div className="relative mt-1">
<DetailsInput value={name || ""} onChange={(e) => setFilename(`${e.target.value}.${extension}`)} />
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className={`sm:text-sm ${getColors('text-gray-500', 'placeholder-[var(--vscode-input-placeholderForeground)]')}`}>.{extension}</span>
<span className={`sm:text-sm placeholder-[var(--vscode-input-placeholderForeground)]`}>.{extension}</span>
</div>
</div>
</div>
<div>
<label className={`block text-sm font-medium ${getColors(
'text-gray-700 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonTitle)}
</label>
<div className="mt-1">
@@ -213,11 +214,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{isImageFile && (
<>
<div>
<label className={`block text-sm font-medium ${getColors(
'text-gray-700 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonCaption)}
</label>
<div className="mt-1">
@@ -225,11 +222,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
</div>
</div>
<div>
<label className={`block text-sm font-medium ${getColors(
'text-gray-700 dark:text-whisper-900',
'text-[var(--vscode-editor-foreground)]'
)
}`}>
<label className={`block text-sm font-medium text-[var(--vscode-editor-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaCommonAlt)}
</label>
<div className="mt-1">
@@ -243,11 +236,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 ${getColors(
'border focus:outline-none focus:ring-2 focus:ring-offset-2 text-white bg-teal-600 hover:bg-teal-700 dark:hover:bg-teal-900 focus:ring-teal-500',
'bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1'
)
}`}
className={`w-full inline-flex justify-center rounded border-transparent shadow-sm px-4 py-2 text-base font-medium sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30 bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] text-[var(--vscode-button-foreground)] outline-[var(--vscode-focusBorder)] outline-1`}
onClick={onSubmitMetadata}
disabled={!filename}
>
@@ -255,11 +244,7 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
</button>
<button
type="button"
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm ${getColors(
'border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200',
'bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]'
)
}`}
className={`mt-3 w-full inline-flex justify-center rounded shadow-sm px-4 py-2 text-base font-medium focus:outline-none sm:mt-0 sm:w-auto sm:text-sm bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)] text-[var(--vscode-button-secondaryForeground)]`}
onClick={onEditClose}
>
{l10n.t(LocalizationKey.commonCancel)}
@@ -270,22 +255,14 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{!showForm && (
<>
<h3 className={`text-base flex items-center ${getColors(
'text-vulcan-300 dark:text-whisper-500',
'text-[var(--vscode-foreground)]'
)
}`}>
<h3 className={`text-base flex items-center text-[var(--vscode-foreground)]`}>
<span>{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormMetadataTitle)}</span>
<button onClick={onEdit}>
<PencilAltIcon className="w-4 h-4 ml-2" aria-hidden="true" />
<span className="sr-only">{l10n.t(LocalizationKey.commonEdit)}</span>
</button>
</h3>
<dl className={`mt-2 border-t border-b divide-y ${getColors(
'border-gray-200 dark:border-vulcan-200 divide-gray-200 dark:divide-vulcan-200',
'border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]'
)
}`}>
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFieldFileName)} details={media.filename} />
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaCommonTitle)} details={media.title || ""} />
@@ -302,18 +279,10 @@ export const DetailsSlideOver: React.FunctionComponent<IDetailsSlideOverProps> =
{!showForm && (
<div>
<h3 className={`text-base ${getColors(
'text-vulcan-300 dark:text-whisper-500',
'text-[var(--vscode-foreground)]'
)
}`}>
<h3 className={`text-base text-[var(--vscode-foreground)]`}>
{l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormInformationTitle)}
</h3>
<dl className={`mt-2 border-t border-b divide-y ${getColors(
'border-gray-200 dark:border-vulcan-200 divide-gray-200 dark:divide-vulcan-200',
'border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]'
)
}`}>
<dl className={`mt-2 border-t border-b divide-y border-[var(--frontmatter-border)] divide-[var(--frontmatter-border)]`}>
{createdDate && (
<DetailsItem title={l10n.t(LocalizationKey.dashboardMediaMetadataPanelFormInformationCreatedDate)} details={format(createdDate, 'MMM dd, yyyy')} />
)}
@@ -1,8 +1,9 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { UploadIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import {
DashboardViewAtom,
LoadingAtom,
MediaFoldersAtom,
SelectedMediaFolderAtom,
@@ -28,6 +29,7 @@ import { MediaInfo } from '../../../models';
import useThemeColors from '../../hooks/useThemeColors';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { NavigationType } from '../../models';
export interface IMediaProps { }
@@ -41,6 +43,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
const folders = useRecoilValue(MediaFoldersAtom);
const loading = useRecoilValue(LoadingAtom);
const { getColors } = useThemeColors();
const [, setView] = useRecoilState(DashboardViewAtom);
const currentStaticFolder = useMemo(() => {
if (settings?.staticFolder) {
@@ -150,6 +153,7 @@ export const Media: React.FunctionComponent<IMediaProps> = (
);
useEffect(() => {
setView(NavigationType.Media);
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewMediaView
});
@@ -2,14 +2,14 @@ import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, PlusSmIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { FeatureFlag } from '../../../components/features/FeatureFlag';
import { FEATURE_FLAG } from '../../../constants';
import { TelemetryEvent } from '../../../constants/TelemetryEvent';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { DashboardMessage } from '../../DashboardMessage';
import useThemeColors from '../../hooks/useThemeColors';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { DashboardViewAtom, ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { FilterInput } from '../Header/FilterInput';
import { PageLayout } from '../Layout/PageLayout';
import { FormDialog } from '../Modals/FormDialog';
@@ -18,6 +18,7 @@ import { Item } from './Item';
import { NewForm } from './NewForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { NavigationType } from '../../models';
export interface ISnippetsProps { }
@@ -34,6 +35,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
const [mediaSnippet, setMediaSnippet] = useState(false);
const [snippetFilter, setSnippetFilter] = useState<string>('');
const { getColors } = useThemeColors();
const [, setView] = useRecoilState(DashboardViewAtom);
const snippets = settings?.snippets || {};
const snippetKeys = useMemo(() => {
@@ -82,6 +84,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (
};
useEffect(() => {
setView(NavigationType.Snippets);
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewSnippetsView
});
@@ -2,12 +2,12 @@ import { Messenger } from '@estruyf/vscode/dist/client';
import { ChevronRightIcon, DownloadIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { TelemetryEvent } from '../../../constants';
import { TaxonomyData } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { Page } from '../../models';
import { SettingsSelector } from '../../state';
import { NavigationType, Page } from '../../models';
import { DashboardViewAtom, SettingsSelector } from '../../state';
import { NavigationBar, NavigationItem } from '../Layout';
import { PageLayout } from '../Layout/PageLayout';
import { SponsorMsg } from '../Layout/SponsorMsg';
@@ -25,6 +25,7 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
const settings = useRecoilValue(SettingsSelector);
const [taxonomySettings, setTaxonomySettings] = useState<TaxonomyData>();
const [selectedTaxonomy, setSelectedTaxonomy] = useState<string | null>(`tags`);
const [, setView] = useRecoilState(DashboardViewAtom);
const onImport = () => {
Messenger.send(DashboardMessage.importTaxonomy);
@@ -39,6 +40,7 @@ export const TaxonomyView: React.FunctionComponent<ITaxonomyViewProps> = ({
}, [settings?.tags, settings?.categories, settings?.customTaxonomy]);
useEffect(() => {
setView(NavigationType.Taxonomy);
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewTaxonomyDashboard
});
@@ -11,6 +11,9 @@ import useThemeColors from '../../hooks/useThemeColors';
import { WelcomeLink } from './WelcomeLink';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { DashboardViewAtom } from '../../state';
import { useRecoilState } from 'recoil';
import { NavigationType } from '../../models';
export interface IWelcomeScreenProps {
settings: Settings;
@@ -20,8 +23,10 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({
settings
}: React.PropsWithChildren<IWelcomeScreenProps>) => {
const { getColors } = useThemeColors();
const [, setView] = useRecoilState(DashboardViewAtom);
React.useEffect(() => {
setView(NavigationType.Welcome);
Messenger.send(DashboardMessage.sendTelemetry, {
event: TelemetryEvent.webviewWelcomeScreen
});
@@ -1,4 +1,5 @@
export enum NavigationType {
Welcome = 'welcome',
Contents = 'contents',
Media = 'media',
Data = 'data',