#401 - Enable paging on the content dashboard

This commit is contained in:
Elio Struyf
2022-09-08 11:38:45 +02:00
parent bf98ff9a1d
commit 2275c1b9cc
12 changed files with 81 additions and 27 deletions
+6
View File
@@ -41,6 +41,12 @@
"description": "Panel styles",
"type": "file",
"groupId": "panel"
},
{
"name": "settings.ts",
"path": "src/constants/settings.ts",
"description": "Settings names",
"type": "file"
}
],
}
+1
View 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
+6
View File
@@ -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",
+1
View File
@@ -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 -5
View File
@@ -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 }>>) => {
+1
View File
@@ -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 {
+4 -2
View File
@@ -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"),