#178 - Sorting for media files

This commit is contained in:
Elio Struyf
2021-11-17 14:18:04 +01:00
parent e6a7a9aae7
commit 8a0d308ceb
15 changed files with 126 additions and 42 deletions
+1
View File
@@ -6,6 +6,7 @@
- Updated camera icon from VS Code to media icon
- Updated `check` icon on media inserts to `plus` icon
- [#178](https://github.com/estruyf/vscode-front-matter/issues/178): Sorting added to the media dashboard
- [#179](https://github.com/estruyf/vscode-front-matter/issues/179): Updated the `open dashboard` icon to make it easier to spot it
## [5.5.0] - 2021-11-15
+36 -8
View File
@@ -4,7 +4,7 @@ import { basename, dirname, extname, join, parse } from "path";
import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode";
import { Settings as SettingsHelper } from '../helpers';
import { DraftField, Framework, SortingSetting, TaxonomyType } from '../models';
import { DraftField, Framework, SortingSetting, SortOrder, TaxonomyType } from '../models';
import { Folders } from './Folders';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
@@ -26,6 +26,7 @@ import { parseWinPath } from '../helpers/parseWinPath';
import { DateHelper } from '../helpers/DateHelper';
import { FrameworkDetector } from '../helpers/FrameworkDetector';
import { ContentType } from '../helpers/ContentType';
import { SortingOption } from '../dashboardWebView/models';
export class Dashboard {
private static webview: WebviewPanel | null = null;
@@ -174,7 +175,7 @@ export class Dashboard {
Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace");
break;
case DashboardMessage.getMedia:
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder);
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder, msg?.data?.sorting);
break;
case DashboardMessage.copyToClipboard:
env.clipboard.writeText(msg.data);
@@ -299,7 +300,12 @@ export class Dashboard {
crntFramework: SettingsHelper.get<string>(SETTINGS_FRAMEWORK_ID),
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
dashboardState: {
sorting: await ext.getState<ViewType | undefined>(ExtensionState.Dashboard.Sorting, "workspace")
contents: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace")
},
media: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace")
}
}
} as Settings
});
@@ -334,16 +340,19 @@ export class Dashboard {
/**
* Retrieve all media files
*/
private static async getMedia(page: number = 0, requestedFolder: string = '') {
private static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = SettingsHelper.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const contentFolders = Folders.get();
const viewData = Dashboard.viewData;
let selectedFolder = requestedFolder;
const ext = Extension.getInstance();
const crntSort = sort === null ? await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort;
// If the static folder is not set, retreive the last opened location
if (!selectedFolder) {
const stateValue = await Extension.getInstance().getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
const stateValue = await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace");
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
@@ -400,15 +409,19 @@ export class Dashboard {
}
allMedia = allMedia.sort((a, b) => {
if (b.fsPath < a.fsPath) {
if (a.fsPath.toLowerCase() < b.fsPath.toLowerCase()) {
return -1;
}
if (b.fsPath > a.fsPath) {
if (a.fsPath.toLowerCase() > b.fsPath.toLowerCase()) {
return 1;
}
return 0;
});
if (crntSort?.order === SortOrder.desc) {
allMedia = allMedia.reverse();
}
Dashboard.media = Object.assign([], allMedia);
let files: MediaInfo[] = Dashboard.media;
@@ -459,13 +472,28 @@ export class Dashboard {
// Store the last opened folder
await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace");
let sortedFolders = [...allContentFolders, ...allFolders];
sortedFolders = sortedFolders.sort((a, b) => {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return 0;
});
if (crntSort?.order === SortOrder.desc) {
sortedFolders = sortedFolders.reverse();
}
Dashboard.postWebviewMessage({
command: DashboardCommand.media,
data: {
media: files,
total: total,
folders: [...allContentFolders, ...allFolders],
folders: sortedFolders,
selectedFolder
} as MediaPaths
});
+6 -1
View File
@@ -7,6 +7,11 @@ export const ExtensionState = {
MoveTemplatesFolder: `frontMatter:Templates:Move`,
Dashboard: {
Sorting: `frontMatter:Dashboard:Sorting`,
Contents: {
Sorting: `frontMatter:Dashboard:Contents:Sorting`,
},
Media: {
Sorting: `frontMatter:Dashboard:Media:Sorting`,
}
}
};
@@ -6,6 +6,7 @@ import { Header } from '../Header';
import { Overview } from './Overview';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
import usePages from '../../hooks/usePages';
export interface IContentsProps {
pages: Page[];
@@ -14,19 +15,20 @@ export interface IContentsProps {
export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loading}: React.PropsWithChildren<IContentsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const { pageItems } = usePages(pages);
const pageFolders = [...new Set(pages.map(page => page.fmFolder))];
const pageFolders = [...new Set(pageItems.map(page => page.fmFolder))];
return (
<main className={`h-full w-full`}>
<div className="flex flex-col h-full overflow-auto">
<Header
folders={pageFolders}
totalPages={pages.length}
totalPages={pageItems.length}
settings={settings} />
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pages} settings={settings} /> }
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} />
@@ -2,12 +2,12 @@ import * as React from 'react';
import { Spinner } from './Spinner';
import useMessages from '../hooks/useMessages';
import useDarkMode from '../../hooks/useDarkMode';
import usePages from '../hooks/usePages';
import { WelcomeScreen } from './WelcomeScreen';
import { useRecoilValue } from 'recoil';
import { DashboardViewSelector } from '../state';
import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { ViewType } from '../models';
export interface IDashboardProps {
showWelcome: boolean;
@@ -15,7 +15,6 @@ export interface IDashboardProps {
export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome}: React.PropsWithChildren<IDashboardProps>) => {
const { loading, pages, settings } = useMessages();
const { pageItems } = usePages(pages);
const view = useRecoilValue(DashboardViewSelector);
useDarkMode();
@@ -31,9 +30,9 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
return <WelcomeScreen settings={settings} />;
}
if (view === 'media') {
if (view === ViewType.Media) {
return <Media />;
}
return <Contents pages={pageItems} loading={loading} />;
return <Contents pages={pages} loading={loading} />;
};
@@ -2,8 +2,10 @@ import { CollectionIcon } from '@heroicons/react/outline';
import { basename, join } from 'path';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Sorting } from '.';
import { HOME_PAGE_NAVIGATION_ID } from '../../../constants';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { ViewType } from '../../models';
import { SelectedMediaFolderAtom, SettingsAtom } from '../../state';
export interface IBreadcrumbProps {}
@@ -63,8 +65,8 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
}, [selectedFolder]);
return (
<nav className="bg-gray-200 text-vulcan-300 dark:bg-vulcan-400 dark:text-whisper-600 border-b border-gray-300 dark:border-vulcan-100 flex py-2" aria-label="Breadcrumb">
<ol role="list" className="w-full mx-auto flex space-x-4 px-5">
<nav className="w-full bg-gray-200 text-vulcan-300 dark:bg-vulcan-400 dark:text-whisper-600 border-b border-gray-300 dark:border-vulcan-100 flex justify-between py-2" aria-label="Breadcrumb">
<ol role="list" className="flex space-x-4 px-5">
<li className="flex">
<div className="flex items-center">
<button onClick={() => setSelectedFolder(HOME_PAGE_NAVIGATION_ID)} className="text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500">
@@ -95,6 +97,10 @@ export const Breadcrumb: React.FunctionComponent<IBreadcrumbProps> = (props: Rea
</li>
))}
</ol>
<div className={`flex px-5`}>
<Sorting view={ViewType.Media} disableCustomSorting />
</div>
</nav>
);
};
@@ -3,14 +3,14 @@ import { Sorting } from './Sorting';
import { Searchbox } from './Searchbox';
import { Filter } from './Filter';
import { Folders } from './Folders';
import { Settings } from '../../models';
import { Settings, ViewType } from '../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { Startup } from '../Startup';
import { Navigation } from '../Navigation';
import { Grouping } from '.';
import { ViewSwitch } from './ViewSwitch';
import { useRecoilState } from 'recoil';
import { CategoryAtom, DashboardViewAtom, TagAtom } from '../../state';
import { useRecoilState, useResetRecoilState } from 'recoil';
import { CategoryAtom, DashboardViewAtom, SortingAtom, TagAtom } from '../../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { ClearFilters } from './ClearFilters';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
@@ -33,6 +33,7 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
const [ view, setView ] = useRecoilState(DashboardViewAtom);
const resetSorting = useResetRecoilState(SortingAtom)
const createContent = () => {
Messenger.send(DashboardMessage.createContent);
@@ -46,22 +47,27 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
Messenger.send(DashboardMessage.createByTemplate);
};
const updateView = (view: ViewType) => {
setView(view);
resetSorting();
}
return (
<div className={`w-full sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
<div className={`px-4 bg-gray-50 dark:bg-vulcan-50 border-b-2 border-gray-200 dark:border-vulcan-200`}>
<div className={`flex items-center justify-start`}>
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("contents")}>
<button className={`p-2 flex items-center ${view === "contents" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Contents)}>
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
</button>
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => setView("media")}>
<button className={`p-2 flex items-center ${view === "media" ? "bg-gray-200 dark:bg-vulcan-200" : ""} hover:bg-gray-100 dark:hover:bg-vulcan-100`} onClick={() => updateView(ViewType.Media)}>
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</button>
</div>
</div>
{
view === "contents" && (
view === ViewType.Contents && (
<>
<div className={`px-4 mt-3 mb-2 flex items-center justify-between`}>
<Searchbox />
@@ -106,14 +112,14 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
<Grouping />
<Sorting />
<Sorting view={ViewType.Contents} />
</div>
</>
)
}
{
view === "media" && (
view === ViewType.Media && (
<>
<Pagination />
<Breadcrumb />
@@ -6,11 +6,15 @@ import { ExtensionState } from '../../../constants';
import { SortOrder, SortType } from '../../../models';
import { SortOption } from '../../constants/SortOption';
import { DashboardMessage } from '../../DashboardMessage';
import { ViewType } from '../../models';
import { SortingOption } from '../../models/SortingOption';
import { SearchSelector, SettingsSelector, SortingAtom } from '../../state';
import { MenuButton, MenuItem, MenuItems } from '../Menu';
export interface ISortingProps {}
export interface ISortingProps {
disableCustomSorting?: boolean;
view: ViewType;
}
export const sortOptions: SortingOption[] = [
{ name: "Last modified", id: SortOption.LastModified, order: SortOrder.desc, type: SortType.string },
@@ -18,23 +22,22 @@ export const sortOptions: SortingOption[] = [
{ name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string },
];
export const Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsWithChildren<ISortingProps>) => {
export const Sorting: React.FunctionComponent<ISortingProps> = ({disableCustomSorting, view}: React.PropsWithChildren<ISortingProps>) => {
const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom);
const searchValue = useRecoilValue(SearchSelector);
const settings = useRecoilValue(SettingsSelector);
const updateSorting = (value: SortingOption) => {
Messenger.send(DashboardMessage.setState, {
key: ExtensionState.Dashboard.Sorting,
key: `${view === ViewType.Contents ? ExtensionState.Dashboard.Contents.Sorting : ExtensionState.Dashboard.Media.Sorting}`,
value: value
})
});
setCrntSorting(value)
};
let allOptions = [...sortOptions];
if (settings?.customSorting) {
let allOptions = view === ViewType.Contents ? [...sortOptions] : [...sortOptions.slice(1)];
if (settings?.customSorting && !disableCustomSorting) {
allOptions = [...allOptions, ...settings.customSorting.map((s) => ({
title: s.title || s.name,
name: s.name,
@@ -44,7 +47,16 @@ export const Sorting: React.FunctionComponent<ISortingProps> = ({}: React.PropsW
}))];
}
let crntSort = allOptions.find(x => x.id === crntSorting?.id) || sortOptions[0];
let crntSortingOption = crntSorting;
if (!crntSortingOption) {
if (view === ViewType.Contents) {
crntSortingOption = settings?.dashboardState?.contents?.sorting || null;
} else if (view === ViewType.Media) {
crntSortingOption = settings?.dashboardState?.media?.sorting || null;
}
}
let crntSort = allOptions.find(x => x.id === crntSortingOption?.id) || sortOptions[0];
return (
<div className="flex items-center">
@@ -3,7 +3,7 @@ import { RefreshIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { DashboardMessage } from '../../DashboardMessage';
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector } from '../../state';
import { LoadingAtom, MediaTotalSelector, PageAtom, SelectedMediaFolderSelector, SortingSelector } from '../../state';
import { FolderCreation } from './FolderCreation';
import { LIMIT } from './Media';
import { PaginationButton } from './PaginationButton';
@@ -12,6 +12,7 @@ export interface IPaginationProps {}
export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.PropsWithChildren<IPaginationProps>) => {
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const crntSorting = useRecoilValue(SortingSelector);
const totalMedia = useRecoilValue(MediaTotalSelector);
const [ , setLoading ] = useRecoilState(LoadingAtom);
const [ page, setPage ] = useRecoilState(PageAtom);
@@ -50,7 +51,8 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page,
folder: selectedFolder || ''
folder: selectedFolder || '',
sorting: crntSorting
});
}, [page]);
@@ -58,11 +60,21 @@ export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page: 0,
folder: selectedFolder || ''
folder: selectedFolder || '',
sorting: crntSorting
});
setPage(0);
}, [selectedFolder]);
React.useEffect(() => {
setLoading(true);
Messenger.send(DashboardMessage.getMedia, {
page,
folder: selectedFolder || '',
sorting: crntSorting
});
}, [crntSorting]);
return (
<nav
className="py-4 px-5 flex items-center justify-between bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100"
+3 -2
View File
@@ -6,6 +6,7 @@ import { Page } from '../models/Page';
import { DashboardViewAtom, SettingsAtom, ViewDataAtom } from '../state';
import { Messenger } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { ViewType } from '../models';
export default function useMessages() {
const [loading, setLoading] = useState<boolean>(false);
@@ -21,8 +22,8 @@ export default function useMessages() {
break;
case DashboardCommand.viewData:
setViewData(message.data.data);
if (message.data.data?.type === 'media') {
setView('media');
if (message.data.data?.type === ViewType.Media) {
setView(ViewType.Media);
}
break;
case DashboardCommand.settings:
+1 -1
View File
@@ -54,7 +54,7 @@ export default function usePages(pages: Page[]) {
let usedSorting = sorting;
if (!usedSorting) {
const lastSort = settings?.dashboardState.sorting;
const lastSort = settings?.dashboardState.contents.sorting;
if (lastSort) {
setSorting(lastSort);
return;
+5
View File
@@ -26,5 +26,10 @@ export interface Settings {
}
export interface DashboardState {
contents: ViewState;
media: ViewState;
}
export interface ViewState {
sorting: SortingOption | null | undefined;
}
+4
View File
@@ -0,0 +1,4 @@
export enum ViewType {
Contents = "contents",
Media = "media"
}
+2
View File
@@ -1,3 +1,5 @@
export * from './Page';
export * from './Settings';
export * from './SortingOption';
export * from './Status';
export * from './ViewType';
@@ -1,6 +1,7 @@
import { atom } from 'recoil';
import { ViewType } from '../../models';
export const DashboardViewAtom = atom<"contents" | "media">({
export const DashboardViewAtom = atom<ViewType>({
key: 'DashboardViewAtom',
default: "contents"
default: ViewType.Contents
});