#73 - List view

This commit is contained in:
Elio Struyf
2021-09-03 16:45:22 +02:00
parent 3b6a7e9b71
commit b8dee7a5c9
14 changed files with 235 additions and 35 deletions

66
.vscode/recoil.code-snippets vendored Normal file
View File

@@ -0,0 +1,66 @@
{
"Recoil Atom": {
"prefix": "sq-atom",
"body": [
"import { atom } from 'recoil';",
"",
"export const ${1:CollectionId}Atom = atom({",
" key: '${1:CollectionId}Atom',",
" default: 1",
"});"
],
"description": "Creates a new atom",
"scope": "typescript"
},
"Recoil Selector (sync)": {
"prefix": "sq-selector-sync",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
" key: '${1:CollectionData}Selector',",
" get: ({get}) => {",
" return get(${2:CollectionIdState});",
" }",
"});"
],
"description": "Creates a new synchronous selector",
"scope": "typescript"
},
"Recoil Selector (async)": {
"prefix": "sq-selector-async",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
" key: '${1:CollectionData}Selector',",
" get: async ({get}) => {",
" return await dataFetch(get(${2:CollectionIdState}));",
" }",
"});"
],
"description": "Creates a new asynchronous selector",
"scope": "typescript"
},
"Recoil selectorFamily": {
"prefix": "sq-selector-fam",
"body": [
"import { selectorFamily } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selectorFamily({",
" key: '${1:CollectionData}Selector',",
" get: id => async () => {",
" return await dataFetch({id});",
" }",
"});"
],
"description": "Creates a selectorFamily (same as selector, but used to provide parameters)",
"scope": "typescript"
},
"useTranslation": {
"prefix": ["sq-translation", "useTranslation"],
"body": "const { t: strings } = useTranslation();",
"description": "Include the translations",
"scope": "typescriptreact"
}
}

15
package-lock.json generated
View File

@@ -2638,6 +2638,12 @@
"strip-bom-string": "^1.0.0"
}
},
"hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE=",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -4758,6 +4764,15 @@
"picomatch": "^2.2.1"
}
},
"recoil": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.4.1.tgz",
"integrity": "sha512-vp6KPwlHOjJ4bJofmdDchmgI9ilMTCoUisK8/WYLl8dThH7e7KmtZttiLgvDb2Em99dUfTEsk8vT8L1nUMgqXQ==",
"dev": true,
"requires": {
"hamt_plus": "1.0.2"
}
},
"reduce-css-calc": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",

View File

@@ -391,6 +391,8 @@
},
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
"@headlessui/react": "1.4.0",
"@heroicons/react": "1.0.4",
"@iarna/toml": "2.2.3",
"@types/glob": "7.1.3",
"@types/js-yaml": "3.12.1",
@@ -410,11 +412,13 @@
"gray-matter": "4.0.2",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
"lodash.uniqby": "4.7.0",
"mdast-util-from-markdown": "1.0.0",
"postcss": "^8.3.6",
"postcss-loader": "4.3.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"recoil": "^0.4.1",
"style-loader": "2.0.0",
"tailwindcss": "^2.2.7",
"ts-loader": "8.0.3",
@@ -422,10 +426,7 @@
"typescript": "4.0.2",
"wc-react": "github:estruyf/wc-react",
"webpack": "4.44.2",
"webpack-cli": "3.3.12",
"@headlessui/react": "1.4.0",
"@heroicons/react": "1.0.4",
"lodash.uniqby": "4.7.0"
"webpack-cli": "3.3.12"
},
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.40"

View File

@@ -13,6 +13,7 @@ import { Button } from '../Button';
import { Navigation } from '../Navigation';
import { Grouping } from '.';
import { GroupOption } from '../../constants/GroupOption';
import { ViewSwitch } from './ViewSwitch';
export interface IHeaderProps {
settings: Settings;
@@ -66,11 +67,11 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({currentTab, curre
</div>
<div className="px-4 flex flex-col lg:flex-row items-center border-b border-gray-200 dark:border-whisper-600">
<div className={`w-full`}>
<div className={`w-full lg:w-auto`}>
<Navigation currentTab={currentTab} totalPages={totalPages} switchTab={switchTab} />
</div>
<div className={`my-4 lg:my-0 w-full flex items-center justify-between order-first lg:order-last `}>
<div className={`my-4 lg:my-0 w-full flex items-center justify-end space-x-4 lg:space-x-6 xl:space-x-8 order-first lg:order-last`}>
<Folders crntFolder={crntFolder} folders={folders} switchFolder={switchFolder} />
<Filter label={`Tag filter`} activeItem={crntTag} items={settings.tags} onClick={switchTag} />
@@ -80,6 +81,8 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({currentTab, curre
<Grouping group={crntGroup} switchGroup={switchGroup} />
<Sorting currentSorting={currentSorting} switchSorting={switchSorting} />
<ViewSwitch />
</div>
</div>
</div>

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { ViewAtom, ViewType } from '../../state';
import { ViewGridIcon, ViewListIcon } from '@heroicons/react/solid';
import { STORAGE_VIEW_TYPE } from '../../constants/Storage';
export interface IViewSwitchProps {}
export const ViewSwitch: React.FunctionComponent<IViewSwitchProps> = (props: React.PropsWithChildren<IViewSwitchProps>) => {
const [ view, setView ] = useRecoilState(ViewAtom);
const toggleView = () => {
const newView = view === ViewType.Grid ? ViewType.List : ViewType.Grid;
window.localStorage.setItem(STORAGE_VIEW_TYPE, newView.toString());
setView(newView);
};
return (
<div className={`flex rounded-sm bg-vulcan-50 lg:mb-1`}>
<button className={`flex items-center p-2 rounded-l-sm ${view === ViewType.Grid ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
<ViewGridIcon className={`w-4 h-4`} />
<span className={`sr-only`}>Change to grid</span>
</button>
<button className={`flex items-center p-2 rounded-r-sm ${view === ViewType.List ? 'bg-teal-500 text-vulcan-500' : 'text-whisper-500'}`} onClick={toggleView}>
<ViewListIcon className={`w-4 h-4`} />
<span className={`sr-only`}>Change to list</span>
</button>
</div>
);
};

View File

@@ -1,47 +1,77 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { MessageHelper } from '../../helpers/MessageHelper';
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';
export interface IItemProps extends Page {}
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, preview }: React.PropsWithChildren<IItemProps>) => {
const view = useRecoilValue(ViewSelector);
let className = '';
if (view === ViewType.Grid) {
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`;
} else if (view === ViewType.List) {
className = `divide-y divide-vulcan-200`;
}
const openFile = () => {
MessageHelper.sendMessage(DashboardMessage.openFile, fmFilePath);
};
return (
<li className="relative">
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100`}
onClick={openFile}>
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
{
preview ? (
<img src={`${preview}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
) : (
<div className={`flex items-center justify-center bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100`}>
<MarkdownIcon className={`h-32 text-vulcan-100 dark:text-whisper-100`} />
</div>
)
}
</div>
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<DateField value={date} />
if (view === ViewType.Grid) {
return (
<li className="relative">
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md hover:shadow-xl dark:hover:bg-vulcan-100`}
onClick={openFile}>
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
{
preview ? (
<img src={`${preview}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
) : (
<div className={`flex items-center justify-center bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100`}>
<MarkdownIcon className={`h-32 text-vulcan-100 dark:text-whisper-100`} />
</div>
)
}
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<div className="p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={!!draft} />
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
</div>
</button>
</li>
);
<DateField value={date} />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{description}</p>
</div>
</button>
</li>
);
} else if (view === ViewType.List) {
return (
<li className="relative">
<button className={`px-5 cursor-pointer w-full text-left grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8 py-2 border-b border-vulcan-50 hover:bg-vulcan-50 hover:bg-opacity-70`} onClick={openFile}>
<div className="col-span-8 font-bold truncate">
{title}
</div>
<div className="col-span-2">
<DateField value={date} />
</div>
<div className="col-span-2">
<Status draft={!!draft} />
</div>
</button>
</li>
);
}
return null;
};

View File

@@ -1,10 +1,36 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { ViewSelector, ViewType } from '../state';
export interface IListProps {}
export const List: React.FunctionComponent<IListProps> = ({children}: React.PropsWithChildren<IListProps>) => {
const view = useRecoilValue(ViewSelector);
let className = '';
if (view === ViewType.Grid) {
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`;
} else if (view === ViewType.List) {
className = `-mx-5`;
}
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">
<ul role="list" className={className}>
{view === ViewType.List && (
<li className="px-5 relative uppercase text-vulcan-100 dark:text-whisper-900 py-2 border-b border-vulcan-50">
<div className={`grid grid-cols-12 gap-x-4 sm:gap-x-6 xl:gap-x-8`}>
<div className="col-span-8">
Title
</div>
<div className="col-span-2">
Date
</div>
<div className="col-span-2">
Status
</div>
</div>
</li>
)}
{children}
</ul>
);

View File

@@ -0,0 +1 @@
export const STORAGE_VIEW_TYPE = 'FrontMatter:ListViewType';

View File

@@ -1,5 +1,6 @@
import * as React from "react";
import { render } from "react-dom";
import { RecoilRoot } from "recoil";
import { Dashboard } from "./components/Dashboard";
import './styles.css';
@@ -12,4 +13,4 @@ declare const acquireVsCodeApi: <T = unknown>() => {
const elm = document.querySelector("#app");
const welcome = elm?.getAttribute("data-showWelcome");
render(<Dashboard showWelcome={!!welcome} />, elm);
render(<RecoilRoot><Dashboard showWelcome={!!welcome} /></RecoilRoot>, elm);

View File

@@ -0,0 +1,14 @@
import { atom } from 'recoil';
import { STORAGE_VIEW_TYPE } from '../../constants/Storage';
export enum ViewType {
Grid = 1,
List
}
const defaultView: number = parseInt(window.localStorage.getItem(STORAGE_VIEW_TYPE) as string);
export const ViewAtom = atom<ViewType>({
key: 'ViewAtom',
default: isNaN(defaultView) ? ViewType.Grid : defaultView
});

View File

@@ -0,0 +1 @@
export * from './ViewAtom';

View File

@@ -0,0 +1,2 @@
export * from './atom';
export * from './selectors';

View File

@@ -0,0 +1,9 @@
import { selector } from 'recoil';
import { ViewAtom } from '..';
export const ViewSelector = selector({
key: 'ViewSelector',
get: ({get}) => {
return get(ViewAtom);
}
});

View File

@@ -0,0 +1 @@
export * from './ViewSelector';