Skip to content

Commit

Permalink
SVG sprite page (#2056)
Browse files Browse the repository at this point in the history
* create page with all icons from the sprite

* add info about file size

* refactoring
  • Loading branch information
tom2drum authored Jul 1, 2024
1 parent 48d6f5b commit 4065e47
Show file tree
Hide file tree
Showing 28 changed files with 227 additions and 25 deletions.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
4 changes: 0 additions & 4 deletions icons/verify-contract.svg

This file was deleted.

2 changes: 2 additions & 0 deletions lib/metadata/getPageOgType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {

// service routes, added only to make typescript happy
'/login': 'Regular page',
'/sprite': 'Regular page',
'/api/metrics': 'Regular page',
'/api/log': 'Regular page',
'/api/media-type': 'Regular page',
'/api/proxy': 'Regular page',
'/api/csrf': 'Regular page',
'/api/healthz': 'Regular page',
'/api/config': 'Regular page',
'/api/sprite': 'Regular page',
'/auth/auth0': 'Regular page',
'/auth/unverified-email': 'Regular page',
};
Expand Down
2 changes: 2 additions & 0 deletions lib/metadata/templates/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {

// service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE,
'/sprite': DEFAULT_TEMPLATE,
'/api/metrics': DEFAULT_TEMPLATE,
'/api/log': DEFAULT_TEMPLATE,
'/api/media-type': DEFAULT_TEMPLATE,
'/api/proxy': DEFAULT_TEMPLATE,
'/api/csrf': DEFAULT_TEMPLATE,
'/api/healthz': DEFAULT_TEMPLATE,
'/api/config': DEFAULT_TEMPLATE,
'/api/sprite': DEFAULT_TEMPLATE,
'/auth/auth0': DEFAULT_TEMPLATE,
'/auth/unverified-email': DEFAULT_TEMPLATE,
};
Expand Down
4 changes: 3 additions & 1 deletion lib/metadata/templates/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {

// service routes, added only to make typescript happy
'/login': '%network_name% login',
'/sprite': '%network_name% SVG sprite',
'/api/metrics': '%network_name% node API prometheus metrics',
'/api/log': '%network_name% node API request log',
'/api/media-type': '%network_name% node API media type',
'/api/proxy': '%network_name% node API proxy',
'/api/csrf': '%network_name% node API CSRF token',
'/api/healthz': '%network_name% node API health check',
'/api/config': '%network_name% node API health check',
'/api/config': '%network_name% node API app config',
'/api/sprite': '%network_name% node API SVG sprite content',
'/auth/auth0': '%network_name% authentication',
'/auth/unverified-email': '%network_name% unverified email',
};
Expand Down
4 changes: 3 additions & 1 deletion lib/mixpanel/getPageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {

// service routes, added only to make typescript happy
'/login': 'Login',
'/sprite': 'Sprite',
'/api/metrics': 'Node API: Prometheus metrics',
'/api/log': 'Node API: Request log',
'/api/media-type': 'Node API: Media type',
'/api/proxy': 'Node API: Proxy',
'/api/csrf': 'Node API: CSRF token',
'/api/healthz': 'Node API: Health check',
'/api/config': 'Node API: Health check',
'/api/config': 'Node API: App config',
'/api/sprite': 'Node API: SVG sprite content',
'/auth/auth0': 'Auth',
'/auth/unverified-email': 'Unverified email',
};
Expand Down
10 changes: 10 additions & 0 deletions nextjs/getServerSideProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ export const login: GetServerSideProps<Props> = async(context) => {
return base(context);
};

export const dev: GetServerSideProps<Props> = async(context) => {
if (!config.app.isDev) {
return {
notFound: true,
};
}

return base(context);
};

export const publicTagsSubmit: GetServerSideProps<Props> = async(context) => {

if (!config.features.publicTagsSubmission.isEnabled) {
Expand Down
2 changes: 2 additions & 0 deletions nextjs/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/api/media-type">
| StaticRoute<"/api/metrics">
| StaticRoute<"/api/proxy">
| StaticRoute<"/api/sprite">
| StaticRoute<"/api-docs">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
Expand All @@ -48,6 +49,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/output-roots">
| StaticRoute<"/public-tags/submit">
| StaticRoute<"/search-results">
| StaticRoute<"/sprite">
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
Expand Down
49 changes: 49 additions & 0 deletions pages/api/sprite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from 'fs';
import type { NextApiRequest, NextApiResponse } from 'next';
import path from 'path';

import config from 'configs/app';

const ROOT_DIR = './icons';
const NAME_PREFIX = ROOT_DIR.replace('./', '') + '/';

interface IconInfo {
name: string;
fileSize: number;
}

const getIconName = (filePath: string) => filePath.replace(NAME_PREFIX, '').replace('.svg', '');

function collectIconNames(dir: string) {
const files = fs.readdirSync(dir, { withFileTypes: true });
let icons: Array<IconInfo> = [];

files.forEach((file) => {
const filePath = path.join(dir, file.name);
const stats = fs.statSync(filePath);

file.name.endsWith('.svg') && icons.push({
name: getIconName(filePath),
fileSize: stats.size,
});

if (file.isDirectory()) {
icons = [ ...icons, ...collectIconNames(filePath) ];
}
});

return icons;
}

export default async function spriteHandler(req: NextApiRequest, res: NextApiResponse) {

if (!config.app.isDev) {
return res.status(404).json({ error: 'Not found' });
}

const icons = collectIconNames(ROOT_DIR);

res.status(200).json({
icons,
});
}
18 changes: 18 additions & 0 deletions pages/sprite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { NextPage } from 'next';
import React from 'react';

import PageNextJs from 'nextjs/PageNextJs';

import Sprite from 'ui/pages/Sprite';

const Page: NextPage = () => {
return (
<PageNextJs pathname="/sprite">
<Sprite/>
</PageNextJs>
);
};

export default Page;

export { dev as getServerSideProps } from 'nextjs/getServerSideProps';
17 changes: 8 additions & 9 deletions public/icons/name.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
| "arrows/north-east"
| "arrows/south-east"
| "arrows/up-down"
| "arrows/up-head"
| "beta_xs"
| "beta"
| "blob"
Expand All @@ -30,10 +31,10 @@
| "clock"
| "coins/bitcoin"
| "collection"
| "contract_verified"
| "contract"
| "contracts_verified"
| "contracts"
| "contracts/regular_many"
| "contracts/regular"
| "contracts/verified_many"
| "contracts/verified"
| "copy"
| "cross"
| "delete"
Expand All @@ -59,7 +60,6 @@
| "files/sol"
| "files/yul"
| "filter"
| "finalized"
| "flame"
| "games"
| "gas_xl"
Expand Down Expand Up @@ -99,7 +99,7 @@
| "publictags"
| "qr_code"
| "refresh"
| "repeat_arrow"
| "repeat"
| "restAPI"
| "rocket_xl"
| "rocket"
Expand Down Expand Up @@ -146,14 +146,13 @@
| "transactions"
| "txn_batches_slim"
| "txn_batches"
| "unfinalized"
| "uniswap"
| "up"
| "user_op_slim"
| "user_op"
| "validator"
| "verification-steps/finalized"
| "verification-steps/unfinalized"
| "verified"
| "verify-contract"
| "wallet"
| "wallets/coinbase"
| "wallets/metamask"
Expand Down
2 changes: 1 addition & 1 deletion ui/address/contract/methods/form/ContractMethodForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
onClick={ onReset }
ml={ 1 }
>
<IconSvg name="repeat_arrow" boxSize={ 5 } mr={ 1 }/>
<IconSvg name="repeat" boxSize={ 5 } mr={ 1 }/>
Reset
</Button>
) }
Expand Down
2 changes: 1 addition & 1 deletion ui/home/indicators/ChainIndicatorItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC

return (
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
Expand Down
2 changes: 1 addition & 1 deletion ui/home/indicators/ChainIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const ChainIndicators = () => {

return (
<Skeleton isLoaded={ !statsQueryResult.isPlaceholderData } display="flex" alignItems="center" color={ diffColor } ml={ 2 }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
Expand Down
2 changes: 1 addition & 1 deletion ui/marketplace/AppSecurityReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const AppSecurityReport = ({
<Text fontWeight="500" fontSize="xs" mb={ 2 } variant="secondary">Smart contracts info</Text>
<Flex alignItems="center" justifyContent="space-between" py={ 1.5 }>
<Flex alignItems="center">
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500" mr={ 1 }/>
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500" mr={ 1 }/>
<Text>Verified contracts</Text>
</Flex>
<Link fontSize="sm" fontWeight="500" onClick={ showAllContracts }>
Expand Down
2 changes: 1 addition & 1 deletion ui/marketplace/MarketplaceAppModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const MarketplaceAppModal = ({
mb={ 6 }
>
<Flex alignItems="center" gap={ 2 } flexWrap="wrap">
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500"/>
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500"/>
<Text>Verified contracts</Text>
<Text fontWeight="500">
{ securityReport?.overallInfo.verifiedNumber ?? 0 } of { securityReport?.overallInfo.totalContractsNumber ?? 0 }
Expand Down
120 changes: 120 additions & 0 deletions ui/pages/Sprite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Flex, Box, Tooltip, useClipboard, useColorModeValue } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import type { StaticRoute } from 'nextjs-routes';
import { route } from 'nextjs-routes';

import useFetch from 'lib/hooks/useFetch';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import FilterInput from 'ui/shared/filters/FilterInput';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle';

const formatFileSize = (fileSizeInBytes: number) => `${ (fileSizeInBytes / 1_024).toFixed(2) } Kb`;

interface IconInfo {
name: string;
fileSize: number;
}

const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
const { hasCopied, onCopy } = useClipboard(name, 1000);
const [ copied, setCopied ] = React.useState(false);

React.useEffect(() => {
if (hasCopied) {
setCopied(true);
} else {
setCopied(false);
}
}, [ hasCopied ]);

return (
<Flex
flexDir="column"
alignItems="center"
whiteSpace="pre-wrap"
wordBreak="break-word"
maxW="100px"
textAlign="center"
onClick={ onCopy }
cursor="pointer"
>
<IconSvg name={ name as IconName } boxSize="100px" bgColor={ bgColor } borderRadius="base"/>
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ copied }>
<Box fontWeight={ 500 } mt={ 2 }>{ name }</Box>
</Tooltip>
<Box color="text_secondary">{ formatFileSize(fileSize) }</Box>
</Flex>
);
};

const Sprite = () => {
const [ searchTerm, setSearchTerm ] = React.useState('');
const bgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');

const fetch = useFetch();
const { data, isFetching, isError } = useQuery({
queryKey: [ 'sprite' ],
queryFn: () => {
const url = route({ pathname: '/node-api/sprite' as StaticRoute<'/api/sprite'>['pathname'] });
return fetch<{ icons: Array<IconInfo> }, unknown>(url);
},
});

const content = (() => {
if (isFetching) {
return <ContentLoader/>;
}

if (isError || !data || !('icons' in data)) {
return <DataFetchAlert/>;
}

const items = data.icons.filter((icon) => icon.name.includes(searchTerm));

if (items.length === 0) {
return <EmptySearchResult text="No icons found"/>;
}

return (
<Flex flexWrap="wrap" fontSize="sm" columnGap={ 5 } rowGap={ 5 } justifyContent="flex-start">
{ items.map((item) => <Item key={ item.name } { ...item } bgColor={ bgColor }/>) }
</Flex>
);
})();

const total = React.useMemo(() => {
if (!data || !('icons' in data)) {
return;
}
return data?.icons.reduce((result, item) => {
result.num++;
result.fileSize += item.fileSize;
return result;
}, { num: 0, fileSize: 0 });
}, [ data ]);

const searchInput = <FilterInput placeholder="Search by name..." onChange={ setSearchTerm } isLoading={ isFetching } minW={{ base: '100%', lg: '300px' }}/>;
const totalEl = total ? <Box ml="auto">Items: { total.num } / Size: { formatFileSize(total.fileSize) }</Box> : null;

const contentAfter = (
<>
{ totalEl }
{ searchInput }
</>
);

return (
<div>
<PageTitle title="SVG sprite 🥤" contentAfter={ contentAfter }/>
{ content }
</div>
);
};

export default React.memo(Sprite);
Loading

0 comments on commit 4065e47

Please sign in to comment.