media dashboard implementation

This commit is contained in:
Elio Struyf
2021-09-06 16:26:13 +02:00
parent bf3dec46bc
commit 86c609f49f
15 changed files with 211 additions and 63 deletions
+28 -1
View File
@@ -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
*/
+1
View File
@@ -0,0 +1 @@
export interface MediaPaths { fsPath: string; vsPath: string | undefined; }
+2 -1
View File
@@ -1,5 +1,6 @@
export enum DashboardCommand {
loading = "loading",
pages = "pages",
settings = "settings"
settings = "settings",
media = "media",
}
+1
View File
@@ -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>
);
};
@@ -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>
) : (
<>
+14 -21
View File
@@ -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 />
);
}
};
+48 -27
View File
@@ -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>
);
};
+51
View File
@@ -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>
);
};
+1 -1
View File
@@ -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>) => {
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
export * from './CategorySelector';
export * from './DashboardViewSelector';
export * from './FolderSelector';
export * from './GroupingSelector';
export * from './SearchSelector';