Skip to content

Commit

Permalink
Add URLs to enable filters (#117)
Browse files Browse the repository at this point in the history
This pull request adds a couple of URLs to enable filters, such as:

- `/{category}` to only show Snaps for that category.
- `/{category}/installed` to only show installed Snaps for that
category.
- `/installed` to only show installed Snaps.
  • Loading branch information
Mrtenz authored Sep 25, 2023
1 parent c9ea6d5 commit ba23c84
Show file tree
Hide file tree
Showing 15 changed files with 679 additions and 28 deletions.
158 changes: 133 additions & 25 deletions gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { fetchBuilder, FileSystemCache } from 'node-fetch-cache';
import path from 'path';
import semver from 'semver/preload';

import { generateImage } from './src/utils/images';
import type { Fields } from './src/utils';
import {
generateCategoryImage,
generateInstalledImage,
generateSnapImage,
} from './src/utils/images';

type Description = {
description: string;
Expand Down Expand Up @@ -140,6 +145,9 @@ export const sourceNodes: GatsbyNode[`sourceNodes`] = async ({
actions,
createNodeId,
createContentDigest,
getNodesByType,
cache,
getCache,
}) => {
const { createNode } = actions;
const { registry, customFetch } = await getRegistry();
Expand Down Expand Up @@ -205,6 +213,51 @@ export const sourceNodes: GatsbyNode[`sourceNodes`] = async ({

await createNode(node);
}

const categories = Object.values(registry.verifiedSnaps).reduce(
(result, snap) => {
if (snap.metadata.category) {
result.add(snap.metadata.category);
}

return result;
},
new Set<string>(),
);

for (const category of categories) {
const node = {
name: category,
parent: null,
children: [],
id: createNodeId(`category__${category}`),
internal: {
type: 'Category',
content: JSON.stringify(category),
contentDigest: createContentDigest(category),
},
};

await createNode(node);
}

const snaps = getNodesByType('Snap') as unknown as Fields<
Queries.Snap,
'icon'
>[];

// The SEO banner that is used on the main `/installed` page. This is
// statically queried by the name.
const installedImage = await generateInstalledImage(snaps);
await createFileNodeFromBuffer({
buffer: installedImage,
name: 'main-installed-banner',
ext: '.png',
createNode,
createNodeId,
cache,
getCache,
});
};

export const createSchemaCustomization: GatsbyNode['createSchemaCustomization'] =
Expand All @@ -216,6 +269,11 @@ export const createSchemaCustomization: GatsbyNode['createSchemaCustomization']
banner: File @link(from: "fields.localFile")
onboard: Boolean
}
type Category implements Node {
banner: File @link(from: "fields.localFile")
installedBanner: File @link(from: "fields.installedLocalFile")
}
`);
};

Expand All @@ -225,34 +283,84 @@ export const onCreateNode: GatsbyNode[`onCreateNode`] = async ({
createNodeId,
cache,
getCache,
getNodesByType,
}) => {
if (node.internal.type !== 'Snap') {
return;
if (node.internal.type === 'Snap') {
const snapNode = node as unknown as Fields<
Queries.Snap,
keyof Queries.Snap
>;

const { createNode, createNodeField } = actions;
const banner = await generateSnapImage(
snapNode.name,
snapNode.author?.name,
snapNode.icon,
);

const bannerNode = await createFileNodeFromBuffer({
buffer: banner,
name: 'banner',
ext: '.png',
parentNodeId: snapNode.id,
createNode,
createNodeId,
cache,
getCache,
});

createNodeField({
node,
name: 'localFile',
value: bannerNode.id,
});
}

const snapNode = node as unknown as SnapNode;
const { createNode, createNodeField } = actions;
if (node.internal.type === 'Category') {
const categoryNode = node as unknown as Fields<
Queries.Category,
keyof Queries.Category
>;

const { createNode, createNodeField } = actions;
const snaps = getNodesByType('Snap').filter(
(snap) => snap.category === categoryNode.name,
) as unknown as Fields<Queries.Snap, keyof Queries.Snap>[];

const banner = await generateCategoryImage(snaps);
const bannerNode = await createFileNodeFromBuffer({
buffer: banner,
name: 'banner',
ext: '.png',
parentNodeId: categoryNode.id,
createNode,
createNodeId,
cache,
getCache,
});

const banner = await generateImage(
snapNode.name,
snapNode.author?.name,
snapNode.icon,
);
createNodeField({
node,
name: 'localFile',
value: bannerNode.id,
});

const bannerNode = await createFileNodeFromBuffer({
buffer: banner,
name: 'banner',
ext: '.png',
parentNodeId: snapNode.id,
createNode,
createNodeId,
cache,
getCache,
});
const installedBanner = await generateInstalledImage(snaps);
const installedBannerNode = await createFileNodeFromBuffer({
buffer: installedBanner,
name: 'installed-banner',
ext: '.png',
parentNodeId: categoryNode.id,
createNode,
createNodeId,
cache,
getCache,
});

createNodeField({
node,
name: 'localFile',
value: bannerNode.id,
});
createNodeField({
node,
name: 'installedLocalFile',
value: installedBannerNode.id,
});
}
};
Binary file added src/assets/images/seo/category.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/seo/count.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/seo/fox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/seo/gradient.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/seo/icon-base.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/seo/installed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ export enum RegistrySnapCategory {

export const SNAP_CATEGORY_LABELS: Record<
RegistrySnapCategory,
{ name: MessageDescriptor; icon: IconName }
{ name: MessageDescriptor; icon: IconName; description: MessageDescriptor }
> = {
[RegistrySnapCategory.Interoperability]: {
name: defineMessage`Interoperability`,
icon: 'interoperability',
description: defineMessage`Connect to non-Ethereum blockchains with MetaMask.`,
},
[RegistrySnapCategory.Notifications]: {
name: defineMessage`Notifications`,
icon: 'notifications',
description: defineMessage`Stay in the know with web3 notifications directly in MetaMask.`,
},
[RegistrySnapCategory.TransactionInsights]: {
name: defineMessage`Transaction Insights`,
icon: 'transactionInsights',
description: defineMessage`Stay informed with insights before you confirm transactions in MetaMask.`,
},
};
4 changes: 4 additions & 0 deletions src/features/filter/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export const filterSlice = createSlice({
state.categories = INITIAL_CATEGORIES;
}
},
setCategory: (state, action: PayloadAction<RegistrySnapCategory>) => {
state.categories = [action.payload];
},
},
});

Expand All @@ -80,6 +83,7 @@ export const {
filterAll,
toggleInstalled,
toggleCategory,
setCategory,
} = filterSlice.actions;

export const getSearchQuery = createSelector(
Expand Down
32 changes: 32 additions & 0 deletions src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ msgstr ""
msgid "{name} is now ready to use."
msgstr ""

#: src/pages/{Category.name}/index.tsx
msgid "{nameText} Snaps on the MetaMask Snaps Directory"
msgstr ""

#: src/components/FooterCopyright.tsx
msgid "©{0} MetaMask. All rights reserved."
msgstr ""
Expand Down Expand Up @@ -46,6 +50,14 @@ msgstr ""
msgid "Back"
msgstr ""

#: src/pages/{Category.name}/installed.tsx
msgid "Browse your installed {nameText} Snaps on the MetaMask Snaps Directory"
msgstr ""

#: src/pages/installed.tsx
msgid "Browse your installed Snaps on the MetaMask Snaps Directory."
msgstr ""

#: src/features/filter/Filter.tsx
msgid "Categories"
msgstr ""
Expand All @@ -59,6 +71,10 @@ msgstr ""
msgid "Check"
msgstr ""

#: src/constants.ts
msgid "Connect to non-Ethereum blockchains with MetaMask."
msgstr ""

#: src/pages/snap/{Snap.location}/{Snap.slug}.tsx
msgid "Contact"
msgstr ""
Expand Down Expand Up @@ -153,6 +169,14 @@ msgstr ""
msgid "Installed"
msgstr ""

#: src/pages/installed.tsx
msgid "Installed Snaps"
msgstr ""

#: src/pages/installed.tsx
msgid "Installed Snaps on the MetaMask Snaps Directory"
msgstr ""

#: src/components/InstallSnapButton.tsx
msgid "Installing {name}"
msgstr ""
Expand Down Expand Up @@ -218,6 +242,14 @@ msgstr ""
msgid "Start exploring blockchain applications in seconds. Trusted by over 30 million users worldwide."
msgstr ""

#: src/constants.ts
msgid "Stay in the know with web3 notifications directly in MetaMask."
msgstr ""

#: src/constants.ts
msgid "Stay informed with insights before you confirm transactions in MetaMask."
msgstr ""

#: src/components/FooterLinks.tsx
#: src/pages/snap/{Snap.location}/{Snap.slug}.tsx
msgid "Support"
Expand Down
89 changes: 89 additions & 0 deletions src/pages/installed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { t } from '@lingui/macro';
import { graphql, navigate } from 'gatsby';
import type { FunctionComponent } from 'react';
import { useEffect } from 'react';

import { toggleInstalled } from '../features';
import { useDispatch } from '../hooks';
import type { Fields } from '../utils';

/**
* This page is used to redirect to the main page, and only showing installed
* snaps.
*
* This page is reachable at `/installed`.
*
* @returns The rendered component.
*/
const Installed: FunctionComponent = () => {
const dispatch = useDispatch();

useEffect(() => {
dispatch(toggleInstalled());

// According to the type definition, `navigate` returns a promise, but in
// practice it does not.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
navigate('/');
}, [dispatch]);

return null;
};

export type HeadProps = {
data: {
file: Fields<Queries.File, 'publicURL'>;
site: {
siteMetadata: Fields<
Queries.SiteSiteMetadata,
'title' | 'description' | 'author' | 'siteUrl'
>;
};
};
};

export const Head: FunctionComponent<HeadProps> = ({ data }) => {
const name = t`Installed Snaps`;
const title = t`Installed Snaps on the MetaMask Snaps Directory`;
const description = t`Browse your installed Snaps on the MetaMask Snaps Directory.`;

const image = `${data.site.siteMetadata.siteUrl}${data.file.publicURL}`;

return (
<>
<html lang="en" />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={name} />
<meta property="og:site_name" content={data.site.siteMetadata.title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta name="og:image" content={image} />
<meta name="og:image:width" content="1200" />
<meta name="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content={data.site.siteMetadata.author} />
<meta name="twitter:title" content={name} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
</>
);
};

export const query = graphql`
query {
file(name: { eq: "main-installed-banner" }) {
publicURL
}
site {
siteMetadata {
title
description
author
siteUrl
}
}
}
`;

export default Installed;
Loading

0 comments on commit ba23c84

Please sign in to comment.