mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-08 09:53:20 +02:00
media dashboard implementation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDERS, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART } from './../constants/settings';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { join } from "path";
|
||||
import { basename, extname, join } from "path";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from "vscode";
|
||||
import { SettingsHelper } from '../helpers';
|
||||
import { TaxonomyType } from '../models';
|
||||
@@ -17,6 +17,7 @@ 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';
|
||||
|
||||
|
||||
export class Dashboard {
|
||||
@@ -129,6 +130,9 @@ export class Dashboard {
|
||||
case DashboardMessage.setPageViewType:
|
||||
Extension.getInstance().setState(EXTENSION_STATE_PAGES_VIEW, msg.data);
|
||||
break;
|
||||
case DashboardMessage.getMedia:
|
||||
Dashboard.getMedia();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -161,6 +165,29 @@ export class Dashboard {
|
||||
Dashboard.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all media files
|
||||
*/
|
||||
private static async getMedia() {
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the markdown pages
|
||||
*/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export interface MediaPaths { fsPath: string; vsPath: string | undefined; }
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum DashboardCommand {
|
||||
loading = "loading",
|
||||
pages = "pages",
|
||||
settings = "settings"
|
||||
settings = "settings",
|
||||
media = "media",
|
||||
}
|
||||
@@ -7,4 +7,5 @@ export enum DashboardMessage {
|
||||
initializeProject = 'initializeProject',
|
||||
reload = 'reload',
|
||||
setPageViewType = 'setPageViewType',
|
||||
getMedia = 'getMedia',
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Page } from '../../models';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { Header } from '../Header';
|
||||
import { Overview } from './Overview';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
|
||||
export interface IContentsProps {
|
||||
pages: Page[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loading}: React.PropsWithChildren<IContentsProps>) => {
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
|
||||
const pageFolders = [...new Set(pages.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}
|
||||
settings={settings} />
|
||||
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pages} settings={settings} /> }
|
||||
</div>
|
||||
|
||||
<SponsorMsg version={settings?.versionInfo} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
+10
-10
@@ -2,18 +2,18 @@ import { Disclosure } from '@headlessui/react';
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { groupBy } from '../../helpers/GroupBy';
|
||||
import { FrontMatterIcon } from '../../viewpanel/components/Icons/FrontMatterIcon';
|
||||
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 { groupBy } from '../../../helpers/GroupBy';
|
||||
import { FrontMatterIcon } from '../../../viewpanel/components/Icons/FrontMatterIcon';
|
||||
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';
|
||||
|
||||
export interface IOverviewProps {
|
||||
pages: Page[];
|
||||
settings: Settings;
|
||||
settings: Settings | null;
|
||||
}
|
||||
|
||||
export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settings}: React.PropsWithChildren<IOverviewProps>) => {
|
||||
@@ -25,7 +25,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settin
|
||||
<div className={`max-w-xl text-center`}>
|
||||
<FrontMatterIcon className={`text-vulcan-300 dark:text-whisper-800 h-32 mx-auto opacity-90 mb-8`} />
|
||||
{
|
||||
settings?.folders?.length > 0 ? (
|
||||
settings && settings?.folders?.length > 0 ? (
|
||||
<p className={`text-xl font-medium`}>No Markdown to show</p>
|
||||
) : (
|
||||
<>
|
||||
@@ -1,12 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Spinner } from './Spinner';
|
||||
import useMessages from '../hooks/useMessages';
|
||||
import { Overview } from './Overview';
|
||||
import { Header } from './Header';
|
||||
import useDarkMode from '../../hooks/useDarkMode';
|
||||
import usePages from '../hooks/usePages';
|
||||
import { SponsorMsg } from './SponsorMsg';
|
||||
import { WelcomeScreen } from './WelcomeScreen';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DashboardViewSelector } from '../state';
|
||||
import { Contents } from './Contents/Contents';
|
||||
import { Media } from './Media/Media';
|
||||
|
||||
export interface IDashboardProps {
|
||||
showWelcome: boolean;
|
||||
@@ -15,10 +16,9 @@ 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();
|
||||
|
||||
const pageFolders = [...new Set(pages.map(page => page.fmFolder))];
|
||||
|
||||
if (!settings) {
|
||||
return <Spinner />;
|
||||
}
|
||||
@@ -30,21 +30,14 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
|
||||
if (!settings.initialized || settings.folders?.length === 0) {
|
||||
return <WelcomeScreen settings={settings} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
folders={pageFolders}
|
||||
totalPages={pageItems.length}
|
||||
settings={settings} />
|
||||
|
||||
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
|
||||
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
|
||||
</div>
|
||||
|
||||
<SponsorMsg version={settings.versionInfo} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
if (view === "contents") {
|
||||
return (
|
||||
<Contents pages={pageItems} loading={loading} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Media />
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -11,23 +11,26 @@ import { Navigation } from '../Navigation';
|
||||
import { Grouping } from '.';
|
||||
import { ViewSwitch } from './ViewSwitch';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { CategoryAtom, TagAtom } from '../../state';
|
||||
import { CategoryAtom, DashboardViewAtom, TagAtom } from '../../state';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ClearFilters } from './ClearFilters';
|
||||
import { MarkdownIcon } from '../../../viewpanel/components/Icons/MarkdownIcon';
|
||||
import { PhotographIcon } from '@heroicons/react/outline';
|
||||
|
||||
export interface IHeaderProps {
|
||||
settings: Settings;
|
||||
settings: Settings | null;
|
||||
|
||||
// Navigation
|
||||
totalPages: number;
|
||||
totalPages?: number;
|
||||
|
||||
// Page folders
|
||||
folders: string[];
|
||||
folders?: string[];
|
||||
}
|
||||
|
||||
export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
|
||||
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
|
||||
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
|
||||
const [ view, setView ] = useRecoilState(DashboardViewAtom);
|
||||
|
||||
const createContent = () => {
|
||||
Messenger.send(DashboardMessage.createContent);
|
||||
@@ -35,39 +38,57 @@ 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={`px-4 my-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
|
||||
<div className={`flex items-center space-x-4`}>
|
||||
<Startup settings={settings} />
|
||||
|
||||
<Button onClick={createContent} disabled={!settings.initialized}>Create content</Button>
|
||||
|
||||
<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")}>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-4 flex flex-row items-center border-b border-gray-200 dark:border-whisper-600 justify-between">
|
||||
<div>
|
||||
<Navigation totalPages={totalPages} />
|
||||
</div>
|
||||
{
|
||||
view === "contents" && (
|
||||
<>
|
||||
<div className={`px-4 my-2 flex items-center justify-between`}>
|
||||
<Searchbox />
|
||||
|
||||
<div>
|
||||
<ViewSwitch />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex items-center space-x-4`}>
|
||||
<Startup settings={settings} />
|
||||
|
||||
<Button onClick={createContent} disabled={!settings?.initialized}>Create content</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end space-x-4 lg:space-x-6 xl:space-x-8 bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-300`}>
|
||||
<ClearFilters />
|
||||
<div className="px-4 flex flex-row items-center border-b border-gray-200 dark:border-vulcan-100 justify-between">
|
||||
<div>
|
||||
<Navigation totalPages={totalPages || 0} />
|
||||
</div>
|
||||
|
||||
<Folders folders={folders} />
|
||||
<div>
|
||||
<ViewSwitch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Filter label={`Tag`} activeItem={crntTag} items={settings.tags} onClick={(value) => setCrntTag(value)} />
|
||||
<div className={`py-4 px-5 w-full flex items-center justify-between lg:justify-end space-x-4 lg:space-x-6 xl:space-x-8 bg-gray-200 border-b border-gray-300 dark:bg-vulcan-400 dark:border-vulcan-100`}>
|
||||
<ClearFilters />
|
||||
|
||||
<Filter label={`Category`} activeItem={crntCategory} items={settings.categories} onClick={(value) => setCrntCategory(value)} />
|
||||
<Folders folders={folders || []} />
|
||||
|
||||
<Grouping />
|
||||
<Filter label={`Tag`} activeItem={crntTag} items={settings?.tags || []} onClick={(value) => setCrntTag(value)} />
|
||||
|
||||
<Sorting />
|
||||
</div>
|
||||
<Filter label={`Category`} activeItem={crntCategory} items={settings?.categories || []} onClick={(value) => setCrntCategory(value)} />
|
||||
|
||||
<Grouping />
|
||||
|
||||
<Sorting />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import * as React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MediaPaths } from '../../../models/MediaPaths';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { SettingsSelector } from '../../state';
|
||||
import { Header } from '../Header';
|
||||
|
||||
export interface IMediaProps {}
|
||||
|
||||
const LIMIT = 25;
|
||||
|
||||
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);
|
||||
|
||||
React.useEffect(() => {
|
||||
Messenger.send(DashboardMessage.getMedia);
|
||||
|
||||
Messenger.listen<MediaPaths[]>((message) => {
|
||||
if (message.command === DashboardCommand.media) {
|
||||
setMedia(message.data);
|
||||
setPage(0);
|
||||
}
|
||||
});
|
||||
}, ['']);
|
||||
|
||||
console.log(crntMedia)
|
||||
|
||||
return (
|
||||
<main className={`h-full w-full`}>
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<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>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { REVIEW_LINK, SPONSOR_LINK } from '../../constants/Links';
|
||||
import { VersionInfo } from '../../models';
|
||||
|
||||
export interface ISponsorMsgProps {
|
||||
version: VersionInfo;
|
||||
version: VersionInfo | undefined;
|
||||
}
|
||||
|
||||
export const SponsorMsg: React.FunctionComponent<ISponsorMsgProps> = ({version}: React.PropsWithChildren<ISponsorMsgProps>) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DashboardMessage } from '../DashboardMessage';
|
||||
import { Settings } from '../models/Settings';
|
||||
|
||||
export interface IStartupProps {
|
||||
settings: Settings;
|
||||
settings: Settings | null;
|
||||
}
|
||||
|
||||
export const Startup: React.FunctionComponent<IStartupProps> = ({settings}: React.PropsWithChildren<IStartupProps>) => {
|
||||
@@ -17,7 +17,7 @@ export const Startup: React.FunctionComponent<IStartupProps> = ({settings}: Reac
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsChecked(!!settings.openOnStart);
|
||||
setIsChecked(!!settings?.openOnStart);
|
||||
}, [settings?.openOnStart]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const DashboardViewAtom = atom<"contents" | "media">({
|
||||
key: 'DashboardViewAtom',
|
||||
default: "contents"
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './CategoryAtom';
|
||||
export * from './DashboardViewAtom';
|
||||
export * from './FolderAtom';
|
||||
export * from './GroupingAtom';
|
||||
export * from './SearchAtom';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { DashboardViewAtom } from '..';
|
||||
|
||||
export const DashboardViewSelector = selector({
|
||||
key: 'DashboardViewSelector',
|
||||
get: ({get}) => {
|
||||
return get(DashboardViewAtom);
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './CategorySelector';
|
||||
export * from './DashboardViewSelector';
|
||||
export * from './FolderSelector';
|
||||
export * from './GroupingSelector';
|
||||
export * from './SearchSelector';
|
||||
|
||||
Reference in New Issue
Block a user