Skip to content

Commit

Permalink
use PluginError in DocumentAssetsPlugin
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-tate committed Oct 22, 2024
1 parent 691d26d commit 87b86e1
Show file tree
Hide file tree
Showing 3 changed files with 372 additions and 5 deletions.
12 changes: 7 additions & 5 deletions packages/plugins/src/DocumentAssetsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import remarkParse from 'remark-parse';
import remarkMdx from 'remark-mdx';
import remarkStringify from 'remark-stringify';
import { VFile } from 'vfile';
import PluginError from './utils/PluginError.js';

interface DocumentAssetsPluginOptions {
/**
Expand Down Expand Up @@ -84,15 +85,16 @@ const DocumentAssetsPlugin: PluginType<Page, DocumentAssetsPluginOptions> = {
const resolvedSrcDir = path.resolve(srcDir);
const resolvedOutputDir = path.resolve(outputDir);
if (!resolvedOutputDir.startsWith(resolvedCwd)) {
throw new Error(`outputDir must be within the current working directory: ${outputDir}`);
throw new PluginError(
'outputDir must be within the current working directory',
resolvedOutputDir
);
}

for (const assetSubDir of assetSubDirs) {
const resolvedAssetSubDir = path.resolve(resolvedSrcDir, assetSubDir);
if (!resolvedAssetSubDir.startsWith(resolvedSrcDir)) {
console.log('ERROR 3');

throw new Error(`Asset subdirectory must be within srcDir: ${srcDir}`);
throw new PluginError('asset subdirectory must be within srcDir', resolvedAssetSubDir);
}

let globbedImageDirs;
Expand All @@ -107,7 +109,7 @@ const DocumentAssetsPlugin: PluginType<Page, DocumentAssetsPluginOptions> = {
}
} catch (err) {
console.error(`Error globbing ${assetSubDir} in ${srcDir}:`, err);
throw err;
throw new PluginError(err.message, assetSubDir);
}

await fsExtra.ensureDir(outputDir);
Expand Down
315 changes: 315 additions & 0 deletions packages/site-components/src/PatternIndex/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import React, { useState, memo } from 'react';
import classnames from 'clsx';
import {
FlexItem,
FlowLayout,
FormField,
FormFieldLabel,
GridLayout,
GridItem,
Input
} from '@salt-ds/core';
import {
Pagination,
P2,
P6,
Caption3,
Link,
useBreakpoint,
usePagination,
TileLink,
useImageComponent
} from '@jpmorganchase/mosaic-components';
import { Dropdown, SelectionChangeHandler } from '@salt-ds/lab';
import styles from './styles.css';

export type PatternItem = {
description: string;
owner: string;
title: string;
contentUrl?: string;
link: string;
tags: string[];
source: 'FIGMA' | 'SOURCE' | 'STORYBOOK';
};

export type PatternIndexProps = {
/** Additional class name for root class override */
className?: string;
/** Number of items per page */
itemsPerPage?: number;
/** Data view */
view: PatternItem[];
};

export type PatternRendererProps = {
/** Tile item */
item: PatternItem;
};

const sourceOptions = ['All', 'Figma', 'Storybook', 'Source'];
const sortOrderOptions = ['A-Z', 'Z-A'];

const PureIframe: React.FC<{ src: string; title: string }> = memo(({ src, title }) => (
<iframe src={src} title={title} width="450" height="253" />
));

function sortPatterns(view: PatternItem[], sort: string): PatternItem[] {
const sortKey = 'title';
if (sort === 'A-Z') {
return view.sort((sortableA, sortableB) =>
sortableA[sortKey].localeCompare(sortableB[sortKey])
);
}
return view.sort((sortableA, sortableB) => sortableB[sortKey].localeCompare(sortableA[sortKey]));
}

export const FigmaRenderer = ({ item }) => {
const { description, eyebrow, link, contentUrl = link, owner, source, title } = item;
const ImageComponent = useImageComponent();
return (
<>
<Caption3 className={styles.owner}>
{owner} / {source}
</Caption3>
<FlowLayout>
<FlexItem grow={1}>
<Link href={item.link} variant="document">
<P2 className={styles.title}>{title}</P2>
</Link>
</FlexItem>
{eyebrow ? (
<FlexItem>
<P6>{eyebrow}</P6>
</FlexItem>
) : null}
</FlowLayout>
<P6 className={styles.description}>{description}</P6>
<Link href={link} variant="component">
<ImageComponent src={contentUrl} />
</Link>
</>
);
};

export const StorybookRenderer = ({ item }) => {
const { description, link, contentUrl = link, owner, source, title } = item;
return (
<>
<Caption3 className={styles.owner}>
{owner} / {source}
</Caption3>
<Link href={link} variant="document">
<P2 className={styles.title}>{title}</P2>
</Link>
<P6 className={styles.description}>{description}</P6>
<PureIframe src={contentUrl} title={item.title} />
</>
);
};

export const ReadmeRenderer = ({ item }) => {
const { action, description, image, link, title } = item;
return (
<TileLink
action={action}
description={description}
image={image}
key={`tileLink-${title}`}
link={link}
title={title}
/>
);
};

const filter = (view: PatternItem[], sourceFilter, ownerFilter, tagFilter, textFilter) => {
if (!sourceFilter && !ownerFilter && !tagFilter) {
return view;
}
return view.filter(item => {
const itemTags = item?.tags || [];
const isSourceFiltered =
!item.source ||
sourceFilter === 'All' ||
(item.source && item.source.toLowerCase() === sourceFilter.toLowerCase());
const isOwnerFiltered =
!item.owner ||
ownerFilter === 'All' ||
(item.owner && item.owner.toLowerCase() === ownerFilter.toLowerCase());
console.log('>>>>>>>> tagFilter', tagFilter, itemTags, item);
const isTagFiltered = tagFilter === 'All' || itemTags?.includes(tagFilter);
const isTextFiltered =
!item.title ||
!textFilter?.length ||
(textFilter?.length && item.title.toLowerCase().indexOf(textFilter.toLowerCase()) >= 0);
return isSourceFiltered && isOwnerFiltered && isTextFiltered && isTagFiltered;
});
};

const PatternRenderers = {
SOURCE: ReadmeRenderer,
FIGMA: FigmaRenderer,
STORYBOOK: StorybookRenderer
};

export const PatternIndex: React.FC<React.PropsWithChildren<PatternIndexProps>> = ({
className,
itemsPerPage = 4,
view
}) => {
const [sourceFilter, setSourceFilter] = useState<string | null>('All');
const [ownerFilter, setOwnerFilter] = useState<string | null>('All');
const [tagFilter, setTagFilter] = useState<string | null>('All');
const [textFilter, setTextFilter] = useState<string>('');
const [sortOrder, setSortOrder] = useState<string | null>(sortOrderOptions[0]);

const breakpoint = useBreakpoint();

const handleTextFilterChange = event => {
const { value } = event.target;
setTextFilter(value);
};

const handleSourceFilterSelect: SelectionChangeHandler<string, 'default'> = (
_e,
selectedItem
) => {
setSourceFilter(selectedItem);
};

const handleOwnerFilterSelect: SelectionChangeHandler<string, 'default'> = (_e, selectedItem) => {
setOwnerFilter(selectedItem);
};

const handleTagFilterSelect: SelectionChangeHandler<string, 'default'> = (_e, selectedItem) => {
setTagFilter(selectedItem);
};

const handleSortOrderSelect: SelectionChangeHandler<string, 'default'> = (_e, selectedItem) => {
setSortOrder(selectedItem);
};

const ownerOptions = view.reduce<string[]>(
(result, viewItem) => {
if (viewItem.owner && result.indexOf(viewItem.owner) === -1) {
result.splice(result.length - 1, 0, viewItem.owner);
return result;
}
return result;
},
['All']
);

const tagOptions = view.reduce<string[]>(
(result, viewItem) => {
const tags = viewItem?.tags || [];
const tagsToAdd = tags.reduce<string[]>((newTags, tag) => {
if (newTags.indexOf(tag) === -1 && result.indexOf(tag) === -1) {
newTags = [...newTags, tag];
}
return newTags;
}, []);
return [...result, ...tagsToAdd];
},
['All']
);

const filteredView = filter(view, sourceFilter, ownerFilter, tagFilter, textFilter);
const { startIndex, endIndex, onPageChange, page, pageCount } = usePagination({
itemsPerPage,
displayedCount: filteredView.length
});
let items = filteredView.slice(startIndex, endIndex);
items = sortOrder ? sortPatterns(items, sortOrder) : items;
console.log('>>>>>>>>> filteredView2', filteredView, items);

return (
<>
<div className={classnames(styles.root, className)}>
<FlowLayout>
<FlexItem>
<FormField className={styles.filterFormField}>
<FormFieldLabel>Filter</FormFieldLabel>
<Input
value={textFilter}
inputProps={{ name: 'filter on text' }}
onChange={handleTextFilterChange}
/>
</FormField>
</FlexItem>
<FlexItem>
<FormField>
<FormFieldLabel>Tags</FormFieldLabel>
<Dropdown
defaultSelected={tagFilter}
selected={tagFilter}
source={tagOptions}
onSelect={handleTagFilterSelect}
/>
</FormField>
</FlexItem>
</FlowLayout>
<FlowLayout>
<FlexItem>
<FormField>
<FormFieldLabel>Source</FormFieldLabel>
<Dropdown
defaultSelected={sourceFilter}
selected={sourceFilter}
source={sourceOptions}
onSelect={handleSourceFilterSelect}
/>
</FormField>
</FlexItem>
<FlexItem>
<FormField>
<FormFieldLabel>Owner</FormFieldLabel>
<Dropdown
defaultSelected={ownerFilter}
selected={ownerFilter}
source={ownerOptions}
onSelect={handleOwnerFilterSelect}
/>
</FormField>
</FlexItem>
<FlexItem grow={1}>
<FormField className={styles.sortFormField}>
<FormFieldLabel>Sort</FormFieldLabel>
<Dropdown
defaultSelected={sortOrder}
selected={sortOrder}
source={sortOrderOptions}
onSelect={handleSortOrderSelect}
/>
</FormField>
</FlexItem>
</FlowLayout>
</div>
{items.length ? (
<>
<div className={styles.content}>
<P6 className={styles.count}>
{startIndex + 1} to {Math.min(endIndex, filteredView.length)} Of {filteredView.length}{' '}
Items
</P6>
<GridLayout className={className} columns={breakpoint !== 'mobile' ? 2 : 1}>
{items.map((item, index) => {
const ItemRenderer = PatternRenderers[item.source];
return (
<GridItem key={`pattern-${item.source}-${item.title}-${index}`}>
<ItemRenderer item={item} />
</GridItem>
);
})}
</GridLayout>
</div>
{items.length ? (
<Pagination onPageChange={onPageChange} page={page} pageCount={pageCount} />
) : null}
</>
) : (
<div className={styles.content}>No Results Found</div>
)}
</>
);
};
Loading

0 comments on commit 87b86e1

Please sign in to comment.