Files
vscode-front-matter/src/dashboardWebView/components/SnippetsView/Snippets.tsx
2025-09-08 21:48:30 +02:00

196 lines
7.6 KiB
TypeScript

import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeBracketIcon, PlusIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { FeatureFlag } from '../../../components/features/FeatureFlag';
import { FEATURE_FLAG, GeneralCommands, WEBSITE_LINKS } from '../../../constants';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { FilterInput } from '../Header/FilterInput';
import { PageLayout } from '../Layout/PageLayout';
import { SponsorMsg } from '../Layout/SponsorMsg';
import { Item } from './Item';
import { NewForm } from './NewForm';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { List } from '../Media/List';
import { SlideOver } from '../Modals/SlideOver';
import { DEFAULT_DASHBOARD_FEATURE_FLAGS } from '../../../constants/DefaultFeatureFlags';
export interface ISnippetsProps { }
export const Snippets: React.FunctionComponent<ISnippetsProps> = () => {
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const mode = useRecoilValue(ModeAtom);
const [snippetTitle, setSnippetTitle] = useState<string>('');
const [snippetDescription, setSnippetDescription] = useState<string>('');
const [snippetBody, setSnippetBody] = useState<string>('');
const [showCreateDialog, setShowCreateDialog] = useState(false);
const [mediaSnippet, setMediaSnippet] = useState(false);
const [snippetFilter, setSnippetFilter] = useState<string>('');
const snippets = settings?.snippets || {};
const snippetKeys = useMemo(() => {
let allSnippetKeys = Object.keys(snippets).sort((a, b) => a.localeCompare(b));
if (viewData?.data?.filePath) {
allSnippetKeys = allSnippetKeys.filter((key) => {
return !snippets[key].isMediaSnippet;
});
}
return allSnippetKeys.filter((key) => {
const value = snippetFilter.toLowerCase();
const keyValue = key.toLowerCase();
const descriptionValue = snippets[key].description?.toLowerCase() || '';
// Contains in key or description, values included in key are ranked higher (sort and fuzzy search)
return keyValue.includes(value) || descriptionValue.includes(value);
});
}, [settings?.snippets, snippetFilter, viewData?.data?.filePath]);
const onSnippetAdd = useCallback(() => {
if (!snippetTitle || !snippetBody) {
reset();
return;
}
const fields = SnippetParser.getFields(snippetBody, []);
Messenger.send(DashboardMessage.addSnippet, {
title: snippetTitle,
description: snippetDescription || '',
body: snippetBody,
fields,
isMediaSnippet: mediaSnippet
});
reset();
}, [snippetTitle, snippetDescription, snippetBody, mediaSnippet]);
const reset = () => {
setShowCreateDialog(false);
setSnippetTitle('');
setSnippetDescription('');
setSnippetBody('');
};
useEffect(() => {
Messenger.send(DashboardMessage.setTitle, l10n.t(LocalizationKey.dashboardHeaderTabsSnippets));
Messenger.send(GeneralCommands.toVSCode.logging.info, {
message: `Snippets view loaded`,
location: 'DASHBOARD'
});
}, []);
return (
<PageLayout
header={
<FeatureFlag
features={mode?.features || DEFAULT_DASHBOARD_FEATURE_FLAGS}
flag={FEATURE_FLAG.dashboard.snippets.manage}>
<div
className={`py-3 px-4 flex items-center justify-between border-b border-[var(--frontmatter-border)]`}
aria-label={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsAriaLabel)}
>
<FilterInput
placeholder={l10n.t(LocalizationKey.commonSearch)}
isReady={true}
autoFocus={(snippetKeys && snippetKeys.length > 0)}
value={snippetFilter}
onChange={(value: string) => setSnippetFilter(value)}
onReset={() => setSnippetFilter('')}
/>
<div className="flex flex-1 justify-end">
<button
className={`inline-flex items-center px-3 py-1 rounded text-xs leading-4 font-medium focus:outline-none text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50`}
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsButtonCreate)}
onClick={() => setShowCreateDialog(true)}
>
<PlusIcon className={`mr-2 h-6 w-6`} />
<span className={`text-sm`}>
{l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsButtonCreate)}
</span>
</button>
</div>
</div>
</FeatureFlag>
}
>
<div className="flex flex-col h-full">
{viewData?.data?.filePath && (
<div className={`text-xl text-center mb-6`}>
<p>
{l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsSelectDescription)}
</p>
</div>
)}
{(snippetKeys && snippetKeys.length > 0) ? (
<List
gap={4}
>
{snippetKeys.map((snippetKey: any, index: number) => (
<Item key={index} snippetKey={snippetKey} snippet={snippets[snippetKey]} />
))}
</List>
) : (
<div className="w-full h-full flex items-center justify-center text-white">
<div className={`flex flex-col items-center text-[var(--frontmatter-text)]`}>
<CodeBracketIcon className="w-32 h-32" />
<p className="text-3xl mt-2">
{l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsEmptyMessage)}
</p>
<p className="text-xl mt-4">
<a
className={`text-[var(--frontmatter-link)] hover:text-[var(--frontmatter-link-hover)]`}
href={WEBSITE_LINKS.docs.snippets}
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsReadMore)}
>
{l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsReadMore)}
</a>
</p>
</div>
</div>
)}
{showCreateDialog && (
<SlideOver
title={l10n.t(LocalizationKey.dashboardSnippetsViewSnippetsFormDialogTitle)}
description={``}
isSaveDisabled={!snippetTitle || !snippetBody}
trigger={onSnippetAdd}
dismiss={reset}
okBtnText={l10n.t(LocalizationKey.commonSave)}
cancelBtnText={l10n.t(LocalizationKey.commonCancel)}
>
<NewForm
title={snippetTitle}
description={snippetDescription}
body={snippetBody}
isMediaSnippet={mediaSnippet}
onMediaSnippetUpdate={(value: boolean) => setMediaSnippet(value)}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)}
/>
</SlideOver>
)}
</div>
<SponsorMsg
beta={settings?.beta}
version={settings?.versionInfo}
isBacker={settings?.isBacker}
/>
<img className='hidden' src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Ffrontmatter.codes%2Fmetrics%2Fdashboards&slug=snippets" alt="Snippets metrics" />
</PageLayout>
);
};