mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-05-06 13:32:30 +02:00
#73 - List view
This commit is contained in:
66
.vscode/recoil.code-snippets
vendored
Normal file
66
.vscode/recoil.code-snippets
vendored
Normal 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
15
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
30
src/pagesView/components/Header/ViewSwitch.tsx
Normal file
30
src/pagesView/components/Header/ViewSwitch.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
1
src/pagesView/constants/Storage.ts
Normal file
1
src/pagesView/constants/Storage.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const STORAGE_VIEW_TYPE = 'FrontMatter:ListViewType';
|
||||
@@ -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);
|
||||
14
src/pagesView/state/atom/ViewAtom.ts
Normal file
14
src/pagesView/state/atom/ViewAtom.ts
Normal 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
|
||||
});
|
||||
1
src/pagesView/state/atom/index.ts
Normal file
1
src/pagesView/state/atom/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ViewAtom';
|
||||
2
src/pagesView/state/index.ts
Normal file
2
src/pagesView/state/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './atom';
|
||||
export * from './selectors';
|
||||
9
src/pagesView/state/selectors/ViewSelector.ts
Normal file
9
src/pagesView/state/selectors/ViewSelector.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { selector } from 'recoil';
|
||||
import { ViewAtom } from '..';
|
||||
|
||||
export const ViewSelector = selector({
|
||||
key: 'ViewSelector',
|
||||
get: ({get}) => {
|
||||
return get(ViewAtom);
|
||||
}
|
||||
});
|
||||
1
src/pagesView/state/selectors/index.ts
Normal file
1
src/pagesView/state/selectors/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ViewSelector';
|
||||
Reference in New Issue
Block a user