mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-06 17:02:54 +02:00
Multi-select hook
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import useSelectedItems from '../../hooks/useSelectedItems';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface IItemSelectionProps {
|
||||
filePath: string;
|
||||
isRowItem?: boolean;
|
||||
}
|
||||
|
||||
export const ItemSelection: React.FunctionComponent<IItemSelectionProps> = ({
|
||||
filePath,
|
||||
isRowItem
|
||||
}: React.PropsWithChildren<IItemSelectionProps>) => {
|
||||
const { onMultiSelect, selectedFiles } = useSelectedItems();
|
||||
|
||||
const cssNames = useMemo(() => {
|
||||
if (isRowItem) {
|
||||
return 'block';
|
||||
}
|
||||
return `${selectedFiles.includes(filePath) ? 'block' : 'hidden'} absolute top-2 left-2`;
|
||||
}, [isRowItem, selectedFiles]);
|
||||
|
||||
return (
|
||||
<div className={`${cssNames} group-hover:block`}>
|
||||
<VSCodeCheckbox
|
||||
style={{
|
||||
boxShadow: isRowItem ? "" : "0 0 3px var(--frontmatter-border-preserve)"
|
||||
}}
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onMultiSelect(filePath);
|
||||
}}
|
||||
checked={selectedFiles.includes(filePath)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { MultiSelectedItemsAtom, SettingsSelector, ViewSelector } from '../../state';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { DateField } from '../Common/DateField';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardViewType } from '../../models';
|
||||
import { ContentActions } from './ContentActions';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Status } from './Status';
|
||||
import * as React from 'react';
|
||||
import useExtensibility from '../../hooks/useExtensibility';
|
||||
@@ -17,7 +17,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { I18nLabel } from './I18nLabel';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IItemProps extends Page { }
|
||||
|
||||
@@ -28,7 +28,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
const draftField = useMemo(() => settings?.draftField, [settings]);
|
||||
const cardFields = useMemo(() => settings?.dashboardState?.contents?.cardFields, [settings?.dashboardState?.contents?.cardFields]);
|
||||
const { escapedTitle, escapedDescription } = useCard(pageData, settings?.dashboardState?.contents?.cardFields);
|
||||
@@ -104,14 +103,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return cardFields && (cardFields.state || cardFields.date);
|
||||
}, [cardFields]);
|
||||
|
||||
const onMultiSelect = useCallback(() => {
|
||||
if (selectedFiles.includes(pageData.fmFilePath)) {
|
||||
setSelectedFiles(selectedFiles.filter((file) => file !== pageData.fmFilePath));
|
||||
} else {
|
||||
setSelectedFiles([...selectedFiles, pageData.fmFilePath]);
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
if (view === DashboardViewType.Grid) {
|
||||
return (
|
||||
<li className="relative">
|
||||
@@ -143,14 +134,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<div className={`${selectedFiles.includes(pageData.fmFilePath) ? 'block' : 'hidden'} group-hover:block absolute top-2 left-2 white`}>
|
||||
<VSCodeCheckbox
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onMultiSelect();
|
||||
}}
|
||||
checked={selectedFiles.includes(pageData.fmFilePath)} />
|
||||
</div>
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<div className="relative p-4 w-full grow">
|
||||
{
|
||||
@@ -251,6 +235,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b hover:bg-opacity-70 border-[var(--frontmatter-border)] hover:bg-[var(--vscode-sideBar-background)]`}
|
||||
>
|
||||
<div className="col-span-8 font-bold truncate flex items-center space-x-4">
|
||||
<ItemSelection filePath={pageData.fmFilePath} isRowItem />
|
||||
|
||||
<button
|
||||
title={escapedTitle ? l10n.t(LocalizationKey.commonOpenWithValue, escapedTitle) : l10n.t(LocalizationKey.commonOpen)}
|
||||
onClick={openFile}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { messageHandler } from '@estruyf/vscode/dist/client';
|
||||
import useCard from '../../hooks/useCard';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IPinnedItemProps extends Page { }
|
||||
|
||||
@@ -21,7 +22,7 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
}, [pageData.fmFilePath]);
|
||||
|
||||
return (
|
||||
<li className='group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)]'>
|
||||
<li className='group flex w-full border border-[var(--frontmatter-border)] rounded bg-[var(--vscode-sideBar-background)] hover:bg-[var(--vscode-list-hoverBackground)] text-[var(--vscode-sideBarTitle-foreground)] relative'>
|
||||
<button onClick={openFile} className='relative h-full w-1/3'>
|
||||
{
|
||||
pageData["fmPreviewImage"] ? (
|
||||
@@ -41,6 +42,8 @@ export const PinnedItem: React.FunctionComponent<IPinnedItemProps> = ({
|
||||
}
|
||||
</button>
|
||||
|
||||
<ItemSelection filePath={pageData.fmFilePath} />
|
||||
|
||||
<button onClick={openFile} className='relative w-2/3 p-4 pr-6 text-left flex items-start'>
|
||||
<p className='font-bold'>{escapedTitle}</p>
|
||||
|
||||
|
||||
@@ -55,8 +55,6 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
path: file
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedFiles([]);
|
||||
}, [selectedFiles]);
|
||||
|
||||
const customScriptActions = React.useMemo(() => {
|
||||
@@ -65,38 +63,42 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
}
|
||||
|
||||
const { scripts } = settings;
|
||||
if (view === NavigationType.Media) {
|
||||
const mediaScripts = (scripts || [])
|
||||
let crntScripts: CustomScript[] = [];
|
||||
if (view === NavigationType.Contents) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk && !script.hidden);
|
||||
} else if (view === NavigationType.Media) {
|
||||
crntScripts = (scripts || [])
|
||||
.filter((script) => script.type === ScriptType.MediaFile && !script.hidden);
|
||||
}
|
||||
|
||||
if (mediaScripts.length > 0) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>Scripts</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
if (crntScripts.length > 0) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className='flex items-center text-[var(--vscode-tab-inactiveForeground)] hover:text-[var(--vscode-tab-activeForeground)] disabled:opacity-50 disabled:hover:text-[var(--vscode-tab-inactiveForeground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>Scripts</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" aria-hidden={true} />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align='start'>
|
||||
{
|
||||
mediaScripts.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
<DropdownMenuContent align='start'>
|
||||
{
|
||||
crntScripts.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id || script.title}
|
||||
onClick={() => runCustomScript(script)}
|
||||
>
|
||||
<CommandLineIcon className="mr-2 h-4 w-4" aria-hidden={true} />
|
||||
<span>{script.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -136,20 +138,20 @@ export const ActionsBar: React.FunctionComponent<IActionsBarProps> = ({
|
||||
<PencilIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonEdit)}</span>
|
||||
</ActionsBarItem>
|
||||
|
||||
{customScriptActions}
|
||||
|
||||
<ActionsBarItem
|
||||
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
onClick={() => setShowAlert(true)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</ActionsBarItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{customScriptActions}
|
||||
|
||||
<ActionsBarItem
|
||||
className='hover:text-[var(--vscode-statusBarItem-errorBackground)]'
|
||||
disabled={selectedFiles.length === 0}
|
||||
onClick={() => setShowAlert(true)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" aria-hidden="true" />
|
||||
<span>{l10n.t(LocalizationKey.commonDelete)}</span>
|
||||
</ActionsBarItem>
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`w-full sticky top-0 z-20 bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]`}>
|
||||
<div className={`mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<div className={`overflow-x-auto mb-0 border-b flex justify-between bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)] border-[var(--frontmatter-border)]`}>
|
||||
<Tabs onNavigate={updateView} />
|
||||
|
||||
<div className='flex items-center space-x-2 pr-4'>
|
||||
@@ -186,7 +186,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`py-2 px-4 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
className={`overflow-x-auto py-2 px-4 w-full flex items-center justify-between lg:justify-end border-b space-x-4 lg:space-x-6 xl:space-x-8 bg-[var(--vscode-panel-background)] border-[var(--frontmatter-border)]`}
|
||||
>
|
||||
<ClearFilters />
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<div className="flex flex-col h-full overflow-y-auto overflow-x-hidden">
|
||||
<Header header={header} folders={folders} totalPages={totalPages} settings={settings} />
|
||||
|
||||
<div
|
||||
|
||||
@@ -17,7 +17,6 @@ import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import {
|
||||
LightboxAtom,
|
||||
MultiSelectedItemsAtom,
|
||||
SelectedItemActionAtom,
|
||||
SelectedMediaFolderSelector,
|
||||
SettingsSelector,
|
||||
@@ -31,8 +30,8 @@ import { LocalizationKey } from '../../../localization';
|
||||
import { ItemMenu } from './ItemMenu';
|
||||
import { getRelPath } from '../../utils';
|
||||
import { Snippet } from '../../../models';
|
||||
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
|
||||
import useMediaInfo from '../../hooks/useMediaInfo';
|
||||
import { ItemSelection } from '../Common/ItemSelection';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
@@ -43,7 +42,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
}: React.PropsWithChildren<IItemProps>) => {
|
||||
const [, setLightbox] = useRecoilState(LightboxAtom);
|
||||
const [, setSelectedItemAction] = useRecoilState(SelectedItemActionAtom);
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [showSnippetSelection, setShowSnippetSelection] = useState(false);
|
||||
const [snippet, setSnippet] = useState<Snippet | undefined>(undefined);
|
||||
@@ -246,14 +244,6 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
return null;
|
||||
}, [media]);
|
||||
|
||||
const onMultiSelect = useCallback(() => {
|
||||
if (selectedFiles.includes(media.fsPath)) {
|
||||
setSelectedFiles(selectedFiles.filter((file) => file !== media.fsPath));
|
||||
} else {
|
||||
setSelectedFiles([...selectedFiles, media.fsPath]);
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
const clearFormData = () => {
|
||||
setShowSnippetFormDialog(false);
|
||||
setSnippet(undefined);
|
||||
@@ -291,14 +281,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({
|
||||
{renderMedia}
|
||||
</div>
|
||||
|
||||
<div className={`${selectedFiles.includes(media.fsPath) ? 'block' : 'hidden'} group-hover:block absolute top-2 left-2 white`}>
|
||||
<VSCodeCheckbox
|
||||
onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onMultiSelect();
|
||||
}}
|
||||
checked={selectedFiles.includes(media.fsPath)} />
|
||||
</div>
|
||||
<ItemSelection filePath={media.fsPath} />
|
||||
|
||||
{hasViewData && (
|
||||
<div
|
||||
|
||||
@@ -14,7 +14,7 @@ export const MenuButton: React.FunctionComponent<IMenuButtonProps> = ({
|
||||
disabled
|
||||
}: React.PropsWithChildren<IMenuButtonProps>) => {
|
||||
return (
|
||||
<div className={`group flex items-center ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`group flex items-center shrink-0 ${disabled ? 'opacity-50' : ''}`}>
|
||||
<div className={`mr-2 font-medium flex items-center text-[var(--vscode-tab-inactiveForeground)]`}>
|
||||
{label}:
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { MultiSelectedItemsAtom } from '../state';
|
||||
|
||||
export default function useSelectedItems() {
|
||||
const [selectedFiles, setSelectedFiles] = useRecoilState(MultiSelectedItemsAtom);
|
||||
|
||||
const onMultiSelect = useCallback((filePath: string) => {
|
||||
if (selectedFiles.includes(filePath)) {
|
||||
setSelectedFiles(selectedFiles.filter((file) => file !== filePath));
|
||||
} else {
|
||||
setSelectedFiles([...selectedFiles, filePath]);
|
||||
}
|
||||
}, [selectedFiles]);
|
||||
|
||||
return {
|
||||
selectedFiles,
|
||||
onMultiSelect
|
||||
};
|
||||
}
|
||||
@@ -411,7 +411,7 @@
|
||||
}
|
||||
|
||||
.question {
|
||||
@apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 py-2 px-4 text-whisper-500;
|
||||
@apply relative ml-auto mr-3 w-5/6 rounded-full rounded-br-none bg-teal-900 px-4 py-2 text-whisper-500;
|
||||
|
||||
&:after {
|
||||
--size: 1rem;
|
||||
|
||||
@@ -52,7 +52,12 @@ export const updateCssVariables = () => {
|
||||
);
|
||||
|
||||
// Borders
|
||||
const borderColor = styles.getPropertyValue('--vscode-panel-border');
|
||||
document.documentElement.style.setProperty('--frontmatter-border', 'var(--vscode-panel-border)');
|
||||
document.documentElement.style.setProperty(
|
||||
'--frontmatter-border-preserve',
|
||||
preserveColor(borderColor) || 'var(--vscode-panel-border)'
|
||||
);
|
||||
|
||||
// Other colors which should be preserved (no opacity)
|
||||
const buttonBackground = styles.getPropertyValue('--vscode-button-background');
|
||||
|
||||
Reference in New Issue
Block a user