mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-05-18 07:15:43 +02:00
#401 - Enable paging on the content dashboard
This commit is contained in:
Vendored
+6
@@ -41,6 +41,12 @@
|
||||
"description": "Panel styles",
|
||||
"type": "file",
|
||||
"groupId": "panel"
|
||||
},
|
||||
{
|
||||
"name": "settings.ts",
|
||||
"path": "src/constants/settings.ts",
|
||||
"description": "Settings names",
|
||||
"type": "file"
|
||||
}
|
||||
],
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
- [#376](https://github.com/estruyf/vscode-front-matter/issues/376): Ability to run scripts after content was created
|
||||
- [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote)
|
||||
- [#379](https://github.com/estruyf/vscode-front-matter/issues/377): New `frontMatter.config.reload` command to reload the configuration file + reinitialize its listeners
|
||||
- [#401](https://github.com/estruyf/vscode-front-matter/issues/401): Content dashboard now has pagination enabled and can be disabled via the `frontMatter.dashboard.content.pagination` setting
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
|
||||
@@ -445,6 +445,12 @@
|
||||
},
|
||||
"scope": "Custom scripts"
|
||||
},
|
||||
"frontMatter.dashboard.content.pagination": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "Specify if you want to enable/disable pagination for your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.content.pagination)",
|
||||
"scope": "Dashboard"
|
||||
},
|
||||
"frontMatter.dashboard.content.cardTags": {
|
||||
"type": "string",
|
||||
"default": "tags",
|
||||
|
||||
@@ -66,6 +66,7 @@ export const SETTING_MEDIA_SUPPORTED_MIMETYPES = "media.supportedMimeTypes";
|
||||
|
||||
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
|
||||
export const SETTING_DASHBOARD_CONTENT_TAGS = "dashboard.content.cardTags";
|
||||
export const SETTING_DASHBOARD_CONTENT_PAGINATION = "dashboard.content.pagination";
|
||||
|
||||
export const SETTING_DATA_FILES = "data.files";
|
||||
export const SETTING_DATA_FOLDERS = "data.folders";
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {ChevronRightIcon} from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { groupBy } from '../../../helpers/GroupBy';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
import { Page } from '../../models/Page';
|
||||
import { Settings } from '../../models/Settings';
|
||||
import { GroupingSelector } from '../../state';
|
||||
import { GroupingSelector, PageAtom } from '../../state';
|
||||
import { PAGE_LIMIT } from '../Header/Pagination';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
|
||||
@@ -19,6 +20,15 @@ export interface IOverviewProps {
|
||||
|
||||
export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settings}: React.PropsWithChildren<IOverviewProps>) => {
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const page = useRecoilValue(PageAtom);
|
||||
|
||||
const pagedPages = useMemo(() => {
|
||||
if (settings?.dashboardState.contents.pagination) {
|
||||
return pages.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT));
|
||||
}
|
||||
|
||||
return pages;
|
||||
}, [pages, page, settings]);
|
||||
|
||||
const groupName = useCallback((groupId, groupedPages) => {
|
||||
if (grouping === GroupOption.Draft) {
|
||||
@@ -89,7 +99,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settin
|
||||
|
||||
return (
|
||||
<List>
|
||||
{pages.map((page, idx) => (
|
||||
{pagedPages.map((page, idx) => (
|
||||
<Item key={`${page.slug}-${idx}`} {...page} />
|
||||
))}
|
||||
</List>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MenuButton, MenuItem, MenuItems } from '../Menu';
|
||||
|
||||
export interface IGroupingProps {}
|
||||
|
||||
export const groupOptions = [
|
||||
export const GROUP_OPTIONS = [
|
||||
{ name: "None", id: GroupOption.none },
|
||||
{ name: "Year", id: GroupOption.Year },
|
||||
{ name: "Draft/Published", id: GroupOption.Draft },
|
||||
@@ -16,7 +16,7 @@ export const groupOptions = [
|
||||
export const Grouping: React.FunctionComponent<IGroupingProps> = ({}: React.PropsWithChildren<IGroupingProps>) => {
|
||||
const [ group, setGroup ] = useRecoilState(GroupingAtom);
|
||||
|
||||
const crntGroup = groupOptions.find(x => x.id === group);
|
||||
const crntGroup = GROUP_OPTIONS.find(x => x.id === group);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
@@ -24,7 +24,7 @@ export const Grouping: React.FunctionComponent<IGroupingProps> = ({}: React.Prop
|
||||
<MenuButton label={`Group by`} title={crntGroup?.name || ""} />
|
||||
|
||||
<MenuItems disablePopper>
|
||||
{groupOptions.map((option) => (
|
||||
{GROUP_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.id}
|
||||
title={option.name}
|
||||
|
||||
@@ -9,8 +9,8 @@ import { Startup } from '../Startup';
|
||||
import { Navigation } from '../Navigation';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilState, useResetRecoilState } from 'recoil';
|
||||
import { CategoryAtom, SortingAtom, TagAtom } from '../../state';
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { CategoryAtom, GroupingSelector, SortingAtom, TagAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MediaHeaderTop } from '../Media/MediaHeaderTop';
|
||||
@@ -23,6 +23,8 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { routePaths } from '../..';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { SyncButton } from './SyncButton';
|
||||
import { PAGE_LIMIT, Pagination } from './Pagination';
|
||||
import { GroupOption } from '../../constants/GroupOption';
|
||||
|
||||
export interface IHeaderProps {
|
||||
header?: React.ReactNode;
|
||||
@@ -38,6 +40,7 @@ export interface IHeaderProps {
|
||||
export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
|
||||
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
|
||||
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
|
||||
const grouping = useRecoilValue(GroupingSelector);
|
||||
const resetSorting = useResetRecoilState(SortingAtom);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@@ -175,6 +178,14 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPage
|
||||
|
||||
<Sorting view={NavigationType.Contents} />
|
||||
</div>
|
||||
|
||||
{
|
||||
(settings?.dashboardState.contents.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && (
|
||||
<div className={`flex justify-center py-2 border-b border-gray-300 dark:border-vulcan-100`}>
|
||||
<Pagination totalPages={totalPages || 0} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { LIMIT } from '../../hooks/useMedia';
|
||||
import { routePaths } from '../..';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
|
||||
export interface IPaginationProps {}
|
||||
export interface IPaginationProps {
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = (props: React.PropsWithChildren<IPaginationProps>) => {
|
||||
export const PAGE_LIMIT = 16;
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = ({ totalPages }: React.PropsWithChildren<IPaginationProps>) => {
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const location = useLocation();
|
||||
|
||||
const totalPages = Math.ceil(totalMedia / LIMIT) - 1;
|
||||
const totalItems: number = useMemo(() => {
|
||||
if (location.pathname === routePaths.contents) {
|
||||
return Math.ceil((totalPages || 0) / PAGE_LIMIT) - 1
|
||||
} else {
|
||||
return Math.ceil(totalMedia / PAGE_LIMIT) - 1;
|
||||
}
|
||||
}, [location.pathname, totalPages, totalMedia]);
|
||||
|
||||
const getButtons = (): number[] => {
|
||||
const maxButtons = 5;
|
||||
@@ -19,12 +32,16 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = (props: Rea
|
||||
const end = page + maxButtons;
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (i >= 0 && i <= totalPages) {
|
||||
if (i >= 0 && i <= totalItems) {
|
||||
buttons.push(i);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center sm:justify-end space-x-2 text-sm">
|
||||
@@ -54,19 +71,19 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = (props: Rea
|
||||
setPage(button)
|
||||
}
|
||||
}
|
||||
className={`${page === button ? 'bg-gray-200 px-2 text-vulcan-500' : 'text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500'} max-h-8`}
|
||||
className={`${page === button ? 'bg-gray-200 px-2 text-vulcan-500' : 'text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500'} max-h-8 rounded-sm`}
|
||||
>{button + 1}</button>
|
||||
))}
|
||||
|
||||
<PaginationButton
|
||||
title="Next"
|
||||
disabled={page >= totalPages}
|
||||
disabled={page >= totalItems}
|
||||
onClick={() => setPage(page + 1)} />
|
||||
|
||||
<PaginationButton
|
||||
title="Last"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage(totalPages)} />
|
||||
disabled={page >= totalItems}
|
||||
onClick={() => setPage(totalItems)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaTotalSelector, PageAtom } from '../../state';
|
||||
import { LIMIT } from '../../hooks/useMedia';
|
||||
import { PAGE_LIMIT } from './Pagination';
|
||||
|
||||
export interface IPaginationStatusProps {}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> =
|
||||
const page = useRecoilValue(PageAtom);
|
||||
|
||||
const getTotalPage = () => {
|
||||
const mediaItems = ((page + 1) * LIMIT);
|
||||
const mediaItems = ((page + 1) * PAGE_LIMIT);
|
||||
if (totalMedia < mediaItems) {
|
||||
return totalMedia;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const PaginationStatus: React.FunctionComponent<IPaginationStatusProps> =
|
||||
return (
|
||||
<div className="hidden sm:flex">
|
||||
<p className="text-sm text-gray-500 dark:text-whisper-900">
|
||||
Showing <span className="font-medium">{(page * LIMIT) + 1}</span> to <span className="font-medium">{getTotalPage()}</span> of{' '}
|
||||
Showing <span className="font-medium">{(page * PAGE_LIMIT) + 1}</span> to <span className="font-medium">{getTotalPage()}</span> of{' '}
|
||||
<span className="font-medium">{totalMedia}</span> results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo, MediaPaths } from '../../models';
|
||||
import { DashboardCommand } from '../DashboardCommand';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SearchSelector, SelectedMediaFolderAtom } from '../state';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SelectedMediaFolderAtom } from '../state';
|
||||
import Fuse from 'fuse.js';
|
||||
import { PAGE_LIMIT } from '../components/Header/Pagination';
|
||||
|
||||
const fuseOptions: Fuse.IFuseOptions<MediaInfo> = {
|
||||
keys: [
|
||||
@@ -18,11 +19,9 @@ const fuseOptions: Fuse.IFuseOptions<MediaInfo> = {
|
||||
includeScore: true
|
||||
};
|
||||
|
||||
export const LIMIT = 16;
|
||||
|
||||
export default function useMedia() {
|
||||
const [ media, setMedia ] = useState<MediaInfo[]>([]);
|
||||
const [ page, setPage ] = useRecoilState(PageAtom);
|
||||
const page = useRecoilValue(PageAtom);
|
||||
const [ searchedMedia, setSearchedMedia ] = useState<MediaInfo[]>([]);
|
||||
const [ , setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const [ , setTotal ] = useRecoilState(MediaTotalAtom);
|
||||
@@ -31,7 +30,7 @@ export default function useMedia() {
|
||||
const search = useRecoilValue(SearchAtom);
|
||||
|
||||
const getMedia = useCallback(() => {
|
||||
return searchedMedia.slice(page * LIMIT, ((page + 1) * LIMIT));
|
||||
return searchedMedia.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT));
|
||||
}, [searchedMedia, page]);
|
||||
|
||||
const messageListener = (message: MessageEvent<EventData<MediaPaths | { key: string, value: any }>>) => {
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ContentsViewState {
|
||||
defaultSorting: string | null | undefined;
|
||||
tags: string | null | undefined;
|
||||
templatesEnabled: boolean | null | undefined;
|
||||
pagination: boolean | null | undefined;
|
||||
}
|
||||
|
||||
export interface MediaViewState extends ContentsViewState {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { basename, join } from "path";
|
||||
import { workspace } from "vscode";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Project } from "../commands/Project";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED, SETTING_GIT_ENABLED } from "../constants";
|
||||
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TEMPLATES_ENABLED, SETTING_GIT_ENABLED, SETTING_DASHBOARD_CONTENT_PAGINATION } from "../constants";
|
||||
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
|
||||
import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from "../models";
|
||||
import { DataFile } from "../models/DataFile";
|
||||
@@ -22,6 +22,7 @@ export class DashboardSettings {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const isInitialized = Project.isInitialized();
|
||||
const gitActions = Settings.get<boolean>(SETTING_GIT_ENABLED);
|
||||
const pagination = Settings.get<boolean>(SETTING_DASHBOARD_CONTENT_PAGINATION)
|
||||
|
||||
return {
|
||||
git: {
|
||||
@@ -53,7 +54,8 @@ export class DashboardSettings {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
|
||||
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT),
|
||||
tags: Settings.get<string>(SETTING_DASHBOARD_CONTENT_TAGS),
|
||||
templatesEnabled: Settings.get<boolean>(SETTING_TEMPLATES_ENABLED)
|
||||
templatesEnabled: Settings.get<boolean>(SETTING_TEMPLATES_ENABLED),
|
||||
pagination: pagination !== undefined ? pagination : true
|
||||
},
|
||||
media: {
|
||||
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
|
||||
|
||||
Reference in New Issue
Block a user