mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-02 15:02:44 +02:00
This commit is contained in:
+114
-22
@@ -1,7 +1,8 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART } from './../constants/settings';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { basename, extname, join } from "path";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from "vscode";
|
||||
import { dirname, extname, join } from "path";
|
||||
import { existsSync, statSync } from "fs";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env } from "vscode";
|
||||
import { SettingsHelper } from '../helpers';
|
||||
import { TaxonomyType } from '../models';
|
||||
import { Folders } from './Folders';
|
||||
@@ -17,12 +18,13 @@ import { Extension } from '../helpers/Extension';
|
||||
import { parseJSON } from 'date-fns';
|
||||
import { ViewType } from '../pagesView/state';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { MediaPaths } from '../models/MediaPaths';
|
||||
import { MediaInfo, MediaPaths } from './../models/MediaPaths';
|
||||
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
private static isDisposed: boolean = true;
|
||||
private static media: MediaInfo[] = [];
|
||||
|
||||
/**
|
||||
* Init the dashboard
|
||||
@@ -131,7 +133,10 @@ export class Dashboard {
|
||||
Extension.getInstance().setState(EXTENSION_STATE_PAGES_VIEW, msg.data);
|
||||
break;
|
||||
case DashboardMessage.getMedia:
|
||||
Dashboard.getMedia();
|
||||
Dashboard.getMedia(msg?.data?.page, msg?.data?.folder)
|
||||
break;
|
||||
case DashboardMessage.copyToClipboard:
|
||||
env.clipboard.writeText(msg.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -142,15 +147,19 @@ export class Dashboard {
|
||||
*/
|
||||
private static async getSettings() {
|
||||
const ext = Extension.getInstance();
|
||||
const config = SettingsHelper.getConfig();
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.settings,
|
||||
data: {
|
||||
wsFolder: wsFolder ? wsFolder.fsPath : '',
|
||||
staticFolder: config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS),
|
||||
folders: Folders.get(),
|
||||
initialized: await Template.isInitialized(),
|
||||
tags: SettingsHelper.getTaxonomy(TaxonomyType.Tag),
|
||||
categories: SettingsHelper.getTaxonomy(TaxonomyType.Category),
|
||||
openOnStart: SettingsHelper.getConfig().get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
openOnStart: config.get(SETTINGS_DASHBOARD_OPENONSTART),
|
||||
versionInfo: ext.getVersion(),
|
||||
pageViewType: await ext.getState<ViewType | undefined>(EXTENSION_STATE_PAGES_VIEW)
|
||||
} as Settings
|
||||
@@ -168,23 +177,80 @@ export class Dashboard {
|
||||
/**
|
||||
* Retrieve all media files
|
||||
*/
|
||||
private static async getMedia() {
|
||||
private static async getMedia(page: number = 0, folder: string = '') {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const config = SettingsHelper.getConfig();
|
||||
const staticFolder = config.get<string>(SETTINGS_CONTENT_STATIC_FOLDERS);
|
||||
|
||||
workspace.findFiles(`${staticFolder || ""}/**/*`).then(async (files) => {
|
||||
const media = files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext);
|
||||
}).map((file) => ({
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.webview?.webview.asWebviewUri(file).toString()
|
||||
} as MediaPaths));
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.media,
|
||||
data: media
|
||||
if (Dashboard.media.length === 0) {
|
||||
const contentFolder = Folders.get();
|
||||
let allMedia: MediaInfo[] = [];
|
||||
|
||||
if (staticFolder) {
|
||||
const files = await workspace.findFiles(`${staticFolder || ""}/**/*`);
|
||||
const media = Dashboard.filterMedia(files);
|
||||
|
||||
allMedia = [...media];
|
||||
}
|
||||
|
||||
if (contentFolder && wsFolder) {
|
||||
for (let i = 0; i < contentFolder.length; i++) {
|
||||
const folder = contentFolder[i];
|
||||
const relFolderPath = folder.path.substring(wsFolder.fsPath.length + 1);
|
||||
const files = await workspace.findFiles(`${relFolderPath}/**/*`);
|
||||
const media = Dashboard.filterMedia(files);
|
||||
|
||||
allMedia = [...allMedia, ...media];
|
||||
}
|
||||
}
|
||||
|
||||
allMedia = allMedia.sort((a, b) => {
|
||||
if (b.fsPath < a.fsPath) {
|
||||
return -1;
|
||||
}
|
||||
if (b.fsPath > a.fsPath) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
Dashboard.media = Object.assign([], allMedia);
|
||||
}
|
||||
|
||||
// Filter the media
|
||||
let files = Dashboard.media;
|
||||
if (folder) {
|
||||
files = files.filter(f => f.fsPath.includes(folder));
|
||||
}
|
||||
|
||||
// Retrieve the total after filtering and before the slicing happens
|
||||
const total = files.length;
|
||||
|
||||
// Get media set
|
||||
files = files.slice(page * 16, ((page + 1) * 16));
|
||||
files = files.map((file) => ({
|
||||
...file,
|
||||
stats: statSync(file.fsPath)
|
||||
}));
|
||||
|
||||
const folders = [...new Set(Dashboard.media.map((file) => {
|
||||
let relFolderPath = wsFolder ? file.fsPath.substring(wsFolder.fsPath.length + 1) : file.fsPath;
|
||||
if (staticFolder && relFolderPath.startsWith(staticFolder)) {
|
||||
relFolderPath = relFolderPath.substring(staticFolder.length);
|
||||
}
|
||||
if (relFolderPath?.startsWith('/')) {
|
||||
relFolderPath = relFolderPath.substring(1);
|
||||
}
|
||||
return dirname(relFolderPath);
|
||||
}))];
|
||||
|
||||
Dashboard.postWebviewMessage({
|
||||
command: DashboardCommand.media,
|
||||
data: {
|
||||
media: files,
|
||||
total: total,
|
||||
folders
|
||||
} as MediaPaths
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,10 +294,22 @@ export class Dashboard {
|
||||
};
|
||||
|
||||
if (article?.data.preview && wsFolder) {
|
||||
const previewPath = join(wsFolder.fsPath, staticFolder || "", article?.data.preview);
|
||||
const previewUri = Uri.file(previewPath);
|
||||
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
|
||||
page.preview = preview?.toString() || "";
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", article?.data.preview);
|
||||
const contentFolderPath = join(dirname(file.filePath), article?.data.preview);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.webview?.webview.asWebviewUri(previewUri);
|
||||
page.preview = preview?.toString() || "";
|
||||
} else {
|
||||
page.preview = "";
|
||||
}
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
@@ -250,6 +328,20 @@ export class Dashboard {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the media files
|
||||
*/
|
||||
private static filterMedia(files: Uri[]) {
|
||||
return files.filter(file => {
|
||||
const ext = extname(file.fsPath);
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.svg'].includes(ext);
|
||||
}).map((file) => ({
|
||||
fsPath: file.fsPath,
|
||||
vsPath: Dashboard.webview?.webview.asWebviewUri(file).toString(),
|
||||
stats: undefined
|
||||
} as MediaInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Post data to the dashboard
|
||||
* @param msg
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
export interface MediaPaths { fsPath: string; vsPath: string | undefined; }
|
||||
import { Stats } from "fs";
|
||||
|
||||
export interface MediaPaths {
|
||||
media: MediaInfo[];
|
||||
total: number;
|
||||
folders: string[];
|
||||
}
|
||||
|
||||
export interface MediaInfo {
|
||||
fsPath: string;
|
||||
vsPath: string | undefined;
|
||||
stats: Stats | undefined;
|
||||
}
|
||||
@@ -8,4 +8,5 @@ export enum DashboardMessage {
|
||||
reload = 'reload',
|
||||
setPageViewType = 'setPageViewType',
|
||||
getMedia = 'getMedia',
|
||||
copyToClipboard = 'copyToClipboard',
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../viewpanel/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../DashboardMessage';
|
||||
import { Page } from '../models/Page';
|
||||
import { ViewSelector, ViewType } from '../state';
|
||||
import { DateField } from './DateField';
|
||||
import { Status } from './Status';
|
||||
import { MarkdownIcon } from '../../../viewpanel/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { ViewSelector, ViewType } from '../../state';
|
||||
import { DateField } from '../DateField';
|
||||
import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ViewSelector, ViewType } from '../state';
|
||||
import { ViewSelector, ViewType } from '../../state';
|
||||
|
||||
export interface IListProps {}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { GroupOption } from '../../constants/GroupOption';
|
||||
import { Page } from '../../models/Page';
|
||||
import { Settings } from '../../models/Settings';
|
||||
import { GroupingSelector } from '../../state';
|
||||
import { Item } from '../Item';
|
||||
import { List } from '../List';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
|
||||
export interface IOverviewProps {
|
||||
pages: Page[];
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MarkdownIcon } from '../../../viewpanel/components/Icons/MarkdownIcon';
|
||||
import { PhotographIcon } from '@heroicons/react/outline';
|
||||
import { Pagination } from '../Media/Pagination';
|
||||
|
||||
export interface IHeaderProps {
|
||||
settings: Settings | null;
|
||||
@@ -37,15 +38,15 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`w-full max-w-7xl mx-auto sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
|
||||
<div className={`w-full sticky top-0 z-40 bg-gray-100 dark:bg-vulcan-500`}>
|
||||
|
||||
<div className={`px-4 bg-vulcan-50 border-b-2 border-gray-300 dark:border-vulcan-200`}>
|
||||
<div className={`py-2 flex items-center justify-start space-x-6 xl:space-x-8`}>
|
||||
<button className={`flex items-center hover:text-teal-900 ${view === "contents" ? "font-bold" : ""}`} onClick={() => setView("contents")}>
|
||||
<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")}>
|
||||
<MarkdownIcon className={`h-6 w-auto mr-2`} /><span>Contents</span>
|
||||
</button>
|
||||
<button className={`flex items-center hover:text-teal-900 ${view === "media" ? "font-bold" : ""}`} onClick={() => setView("media")}>
|
||||
<PhotographIcon className={`h-5 w-auto mr-2`} /><span>Media</span>
|
||||
<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")}>
|
||||
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +90,12 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
view === "media" && (
|
||||
<Pagination />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { XIcon } from '@heroicons/react/outline';
|
||||
import Downshift from 'downshift';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaFoldersSelector, SelectedMediaFolderAtom } from '../../state';
|
||||
|
||||
export interface IFolderSelectionProps {}
|
||||
|
||||
export const FolderSelection: React.FunctionComponent<IFolderSelectionProps> = (props: React.PropsWithChildren<IFolderSelectionProps>) => {
|
||||
const folders = useRecoilValue(MediaFoldersSelector);
|
||||
const [ selectedFolder, setSelectedFolder ] = useRecoilState(SelectedMediaFolderAtom);
|
||||
const [ focus, setFocus ] = React.useState(false);
|
||||
|
||||
let allFolders: string[] = Object.assign([], folders);
|
||||
allFolders = allFolders.sort((a: string, b: string) => {
|
||||
if (a.toLowerCase() < b.toLowerCase()) return -1;
|
||||
if (a.toLowerCase() > b.toLowerCase()) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Downshift
|
||||
isOpen={focus}
|
||||
selectedItem={selectedFolder}
|
||||
onOuterClick={() => setFocus(false)}
|
||||
onSelect={(selFolder) => {
|
||||
setSelectedFolder(selFolder);
|
||||
setFocus(false);
|
||||
}}>
|
||||
{
|
||||
({
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
getMenuProps,
|
||||
isOpen,
|
||||
inputValue,
|
||||
getRootProps
|
||||
}) => (
|
||||
<div className={`relative flex items-center`}>
|
||||
<label className={`text-sm text-gray-500 dark:text-whisper-900`}>Filter by: </label>
|
||||
|
||||
<div
|
||||
className={`inline-flex items-center`}
|
||||
{...getRootProps({} as any, {suppressRefError: true})}
|
||||
>
|
||||
<input disabled={!!selectedFolder} onFocus={() => setFocus(true)} className={`ml-2 py-1 px-2 sm:text-sm bg-white dark:bg-vulcan-300 border border-gray-300 dark:border-vulcan-100 text-vulcan-500 dark:text-whisper-500 placeholder-gray-400 dark:placeholder-whisper-800 focus:outline-none`} {...getInputProps()} />
|
||||
|
||||
{
|
||||
selectedFolder && (
|
||||
<button title={`Clear`} onClick={() => setSelectedFolder(null)}><XIcon className={`ml-2 h-6 w-6 text-red-500 hover:text-red-800`} /></button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={`${focus ? `block` : `hidden`} top-8 absolute right-0 z-10 mt-2 w-min rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`} {...getMenuProps()}>
|
||||
{isOpen
|
||||
? allFolders
|
||||
.filter((item: string) => !inputValue || item.includes(inputValue))
|
||||
.map((item, index) => (
|
||||
<div
|
||||
className="cursor-pointer text-gray-500 dark:text-whisper-900 block px-4 py-2 text-sm font-medium w-full text-left hover:bg-gray-100 hover:text-gray-700 dark:hover:text-whisper-600 dark:hover:bg-vulcan-100"
|
||||
{...getItemProps({ key: item, index, item })}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Downshift>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClipboardCopyIcon, FolderIcon, PhotographIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaInfo } from '../../../models/MediaPaths';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector } from '../../state';
|
||||
|
||||
export interface IItemProps {
|
||||
media: MediaInfo;
|
||||
}
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWithChildren<IItemProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const getFolder = () => {
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
let relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
|
||||
return dirname(relPath || "");
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
let relPath: string | undefined = "";
|
||||
if (settings?.wsFolder && media.fsPath) {
|
||||
relPath = media.fsPath.split(settings.wsFolder).pop();
|
||||
|
||||
if (settings.staticFolder && relPath) {
|
||||
relPath = relPath.split(settings.staticFolder).pop();
|
||||
}
|
||||
}
|
||||
|
||||
Messenger.send(DashboardMessage.copyToClipboard, relPath || "");
|
||||
};
|
||||
|
||||
const calculateSize = () => {
|
||||
if (media?.stats?.size) {
|
||||
const size = media.stats.size / (1024*1024);
|
||||
if (size > 1) {
|
||||
return `${size.toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(size * 1024).toFixed(2)} KB`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<li className="relative bg-gray-50 dark:bg-vulcan-200 hover:shadow-xl dark:hover:bg-vulcan-100">
|
||||
<div className="bg-white group block w-full aspect-w-10 aspect-h-7 overflow-hidden ">
|
||||
<img src={media.vsPath} alt={basename(media.fsPath)} className="mx-auto object-cover pointer-events-none" />
|
||||
</div>
|
||||
<div className={`relative py-4 pl-4 pr-10`}>
|
||||
<div className={`absolute top-4 right-4`}>
|
||||
<button title={`Copy media path`}
|
||||
className={`hover:text-teal-900 focus:outline-none`}
|
||||
onClick={copyToClipboard}>
|
||||
<ClipboardCopyIcon className={`h-5 w-5`} />
|
||||
<span className={`sr-only`}>Copy media path</span>
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm dark:text-whisper-900 font-bold pointer-events-none flex items-center">
|
||||
{basename(media.fsPath)}
|
||||
</p>
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
<FolderIcon className={`w-5 h-5 mr-1`} /> {getFolder()}
|
||||
</p>
|
||||
{
|
||||
media?.stats?.size && (
|
||||
<p className="mt-2 text-sm dark:text-whisper-900 font-medium pointer-events-none flex items-center">
|
||||
{calculateSize()}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IListProps {}
|
||||
|
||||
export const List: React.FunctionComponent<IListProps> = ({children}: React.PropsWithChildren<IListProps>) => {
|
||||
return (
|
||||
<ul role="list" className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8">
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
@@ -1,35 +1,32 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaPaths } from '../../../models/MediaPaths';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo, MediaPaths } from '../../../models/MediaPaths';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { MediaFoldersAtom, MediaTotalAtom, SettingsSelector } from '../../state';
|
||||
import { Header } from '../Header';
|
||||
import { Item } from './Item';
|
||||
import { List } from './List';
|
||||
|
||||
export interface IMediaProps {}
|
||||
|
||||
const LIMIT = 25;
|
||||
export const LIMIT = 16;
|
||||
|
||||
export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWithChildren<IMediaProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const [ media, setMedia ] = React.useState<MediaPaths[]>([]);
|
||||
const [ page, setPage ] = React.useState<number>(0);
|
||||
|
||||
const crntMedia = media.splice(page * LIMIT, LIMIT);
|
||||
const [ media, setMedia ] = React.useState<MediaInfo[]>([]);
|
||||
const [ , setTotal ] = useRecoilState(MediaTotalAtom);
|
||||
const [ , setFolders ] = useRecoilState(MediaFoldersAtom);
|
||||
|
||||
React.useEffect(() => {
|
||||
Messenger.send(DashboardMessage.getMedia);
|
||||
|
||||
Messenger.listen<MediaPaths[]>((message) => {
|
||||
Messenger.listen<MediaPaths>((message) => {
|
||||
if (message.command === DashboardCommand.media) {
|
||||
setMedia(message.data);
|
||||
setPage(0);
|
||||
setMedia(message.data.media);
|
||||
setTotal(message.data.total);
|
||||
setFolders(message.data.folders);
|
||||
}
|
||||
});
|
||||
}, ['']);
|
||||
|
||||
console.log(crntMedia)
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
@@ -37,13 +34,13 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
<Header settings={settings} />
|
||||
|
||||
<div className="w-full max-w-7xl mx-auto py-6 px-4">
|
||||
{
|
||||
crntMedia.map((media) => (
|
||||
<div key={media.fsPath} className="flex flex-col items-center justify-center w-full h-64">
|
||||
<img src={media.vsPath} className="w-full h-full" />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<List>
|
||||
{
|
||||
media.map((file) => (
|
||||
<Item key={file.fsPath} media={file} />
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { MediaTotalSelector, SelectedMediaFolderSelector } from '../../state';
|
||||
import { FolderSelection } from './FolderSelection';
|
||||
import { LIMIT } from './Media';
|
||||
import { PaginationButton } from './PaginationButton';
|
||||
|
||||
export interface IPaginationProps {}
|
||||
|
||||
export const Pagination: React.FunctionComponent<IPaginationProps> = ({}: React.PropsWithChildren<IPaginationProps>) => {
|
||||
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
|
||||
const totalMedia = useRecoilValue(MediaTotalSelector);
|
||||
const [ page, setPage ] = React.useState<number>(0);
|
||||
|
||||
const totalPages = Math.ceil(totalMedia / LIMIT) - 1;
|
||||
|
||||
const getTotalPage = () => {
|
||||
const mediaItems = ((page + 1) * LIMIT);
|
||||
if (totalMedia < mediaItems) {
|
||||
return totalMedia;
|
||||
}
|
||||
return mediaItems;
|
||||
};
|
||||
|
||||
// Write me function to retrieve buttons before and after current page
|
||||
const getButtons = (): number[] => {
|
||||
const maxButtons = 5;
|
||||
const buttons: number[] = [];
|
||||
const start = page - maxButtons;
|
||||
const end = page + maxButtons;
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (i >= 0 && i <= totalPages) {
|
||||
buttons.push(i);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
};
|
||||
|
||||
const buttons = getButtons();
|
||||
|
||||
React.useEffect(() => {
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page,
|
||||
folder: selectedFolder || ''
|
||||
});
|
||||
}, [page]);
|
||||
|
||||
React.useEffect(() => {
|
||||
Messenger.send(DashboardMessage.getMedia, {
|
||||
page: 0,
|
||||
folder: selectedFolder || ''
|
||||
});
|
||||
setPage(0);
|
||||
}, [selectedFolder]);
|
||||
|
||||
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"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<div className="hidden sm:block">
|
||||
<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{' '}
|
||||
<span className="font-medium">{totalMedia}</span> results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FolderSelection />
|
||||
|
||||
<div className="flex justify-between sm:justify-end space-x-2 text-sm">
|
||||
<PaginationButton
|
||||
title="First"
|
||||
disabled={page === 0}
|
||||
onClick={() => {
|
||||
if (page > 0) {
|
||||
setPage(0)
|
||||
}
|
||||
}} />
|
||||
|
||||
<PaginationButton
|
||||
title="Previous"
|
||||
disabled={page === 0}
|
||||
onClick={() => {
|
||||
if (page > 0) {
|
||||
setPage(page - 1)
|
||||
}
|
||||
}} />
|
||||
|
||||
{buttons.map((button) => (
|
||||
<button
|
||||
key={button}
|
||||
disabled={button === page}
|
||||
onClick={() => {
|
||||
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'}`}
|
||||
>{button + 1}</button>
|
||||
))}
|
||||
|
||||
<PaginationButton
|
||||
title="Next"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage(page + 1)} />
|
||||
|
||||
<PaginationButton
|
||||
title="Last"
|
||||
disabled={page >= totalPages}
|
||||
onClick={() => setPage(totalPages)} />
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IPaginationButtonProps {
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const PaginationButton: React.FunctionComponent<IPaginationButtonProps> = ({title, disabled, onClick}: React.PropsWithChildren<IPaginationButtonProps>) => {
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={`text-gray-500 hover:text-gray-600 dark:text-whisper-900 dark:hover:text-whisper-500 disabled:hover:text-gray-500 dark:disabled:hover:text-whisper-900 disabled:opacity-50`}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,8 @@ import { ViewType } from '../state';
|
||||
import { ContentFolder } from './../../models/ContentFolder';
|
||||
|
||||
export interface Settings {
|
||||
wsFolder: string;
|
||||
staticFolder: string;
|
||||
folders: ContentFolder[];
|
||||
initialized: boolean
|
||||
tags: string[];
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const MediaFoldersAtom = atom<string[]>({
|
||||
key: 'MediaFoldersAtom',
|
||||
default: []
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const MediaTotalAtom = atom<number>({
|
||||
key: 'MediaTotalAtom',
|
||||
default: 0
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const SelectedMediaFolderAtom = atom<string | null>({
|
||||
key: 'SelectedMediaFolderAtom',
|
||||
default: null
|
||||
});
|
||||
@@ -2,7 +2,10 @@ export * from './CategoryAtom';
|
||||
export * from './DashboardViewAtom';
|
||||
export * from './FolderAtom';
|
||||
export * from './GroupingAtom';
|
||||
export * from './MediaFoldersAtom';
|
||||
export * from './MediaTotalAtom';
|
||||
export * from './SearchAtom';
|
||||
export * from './SelectedMediaFolderAtom';
|
||||
export * from './SettingsAtom';
|
||||
export * from './SortingAtom';
|
||||
export * from './TabAtom';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { MediaFoldersAtom } from '..';
|
||||
|
||||
export const MediaFoldersSelector = selector({
|
||||
key: 'MediaFoldersSelector',
|
||||
get: ({get}) => {
|
||||
return get(MediaFoldersAtom);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { MediaTotalAtom } from '..';
|
||||
|
||||
export const MediaTotalSelector = selector({
|
||||
key: 'MediaTotalSelector',
|
||||
get: ({get}) => {
|
||||
return get(MediaTotalAtom);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { SelectedMediaFolderAtom } from '..';
|
||||
|
||||
export const SelectedMediaFolderSelector = selector({
|
||||
key: 'SelectedMediaFolderSelector',
|
||||
get: ({get}) => {
|
||||
return get(SelectedMediaFolderAtom);
|
||||
}
|
||||
});
|
||||
@@ -2,7 +2,10 @@ export * from './CategorySelector';
|
||||
export * from './DashboardViewSelector';
|
||||
export * from './FolderSelector';
|
||||
export * from './GroupingSelector';
|
||||
export * from './MediaFoldersSelector';
|
||||
export * from './MediaTotalSelector';
|
||||
export * from './SearchSelector';
|
||||
export * from './SelectedMediaFolderSelector';
|
||||
export * from './SettingsSelector';
|
||||
export * from './SortingSelector';
|
||||
export * from './TabSelector';
|
||||
|
||||
Reference in New Issue
Block a user