mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Make static sprite registry (#2615)
* make static sprite registry * [skip ci] fix file stats command * [skip ci] sort icons by name
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
/public/assets/configs
|
||||
/public/icons/sprite.svg
|
||||
/public/icons/sprite.*.svg
|
||||
/public/icons/registry.json
|
||||
/public/icons/README.md
|
||||
/public/static/og_image.png
|
||||
/public/sitemap.xml
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ RUN yarn --frozen-lockfile --network-timeout 100000
|
||||
# ****** STAGE 2: Build *******
|
||||
# *****************************
|
||||
FROM node:22.11.0-alpine AS builder
|
||||
RUN apk add --no-cache --upgrade libc6-compat bash
|
||||
RUN apk add --no-cache --upgrade libc6-compat bash jq
|
||||
|
||||
# pass build args to env variables
|
||||
ARG GIT_COMMIT_SHA
|
||||
|
||||
@@ -1,20 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
yarn icons build -i ./icons -o ./public/icons --optimize
|
||||
icons_dir="./icons"
|
||||
target_dir="./public/icons"
|
||||
|
||||
yarn icons build -i $icons_dir -o $target_dir --optimize
|
||||
|
||||
create_registry_file() {
|
||||
# Create a temporary file to store the registry
|
||||
local registry_file="$target_dir/registry.json"
|
||||
|
||||
# Start the JSON array
|
||||
echo "[]" > "$registry_file"
|
||||
|
||||
# Detect OS and set appropriate stat command
|
||||
get_file_size() {
|
||||
local file="$1"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
stat -f%z "$file"
|
||||
else
|
||||
# Linux and others
|
||||
stat -c%s "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to process each file
|
||||
process_file() {
|
||||
local file="$1"
|
||||
local relative_path="${file#$icons_dir/}"
|
||||
local file_size=$(get_file_size "$file")
|
||||
|
||||
# Create a temporary file with the new entry
|
||||
jq --arg name "$relative_path" --arg size "$file_size" \
|
||||
'. + [{"name": $name, "file_size": ($size|tonumber)}]' \
|
||||
"$registry_file" > "${registry_file}.tmp"
|
||||
|
||||
# Move the temporary file back
|
||||
mv "${registry_file}.tmp" "$registry_file"
|
||||
}
|
||||
|
||||
# Find all SVG files and process them
|
||||
find "$icons_dir" -type f -name "*.svg" | while read -r file; do
|
||||
process_file "$file"
|
||||
done
|
||||
}
|
||||
|
||||
# Skip hash creation and renaming for playwright environment
|
||||
if [ "$NEXT_PUBLIC_APP_ENV" != "pw" ]; then
|
||||
# Generate hash from the sprite file
|
||||
HASH=$(md5sum ./public/icons/sprite.svg | cut -d' ' -f1 | head -c 8)
|
||||
HASH=$(md5sum $target_dir/sprite.svg | cut -d' ' -f1 | head -c 8)
|
||||
|
||||
# Remove old sprite files
|
||||
rm -f ./public/icons/sprite.*.svg
|
||||
rm -f $target_dir/sprite.*.svg
|
||||
|
||||
# Rename the new sprite file
|
||||
mv ./public/icons/sprite.svg "./public/icons/sprite.${HASH}.svg"
|
||||
mv $target_dir/sprite.svg "$target_dir/sprite.${HASH}.svg"
|
||||
|
||||
export NEXT_PUBLIC_ICON_SPRITE_HASH=${HASH}
|
||||
|
||||
# Skip registry creation in development environment
|
||||
# just to make the dev build faster
|
||||
# remove this condition if you want to create the registry file in development environment
|
||||
if [ "$NEXT_PUBLIC_APP_ENV" != "development" ]; then
|
||||
create_registry_file
|
||||
fi
|
||||
|
||||
echo "SVG sprite created: sprite.${HASH}.svg"
|
||||
else
|
||||
echo "SVG sprite created: sprite.svg (hash skipped for playwright environment)"
|
||||
|
||||
@@ -72,7 +72,6 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
|
||||
'/api/csrf': 'Regular page',
|
||||
'/api/healthz': 'Regular page',
|
||||
'/api/config': 'Regular page',
|
||||
'/api/sprite': 'Regular page',
|
||||
};
|
||||
|
||||
export default function getPageOgType(pathname: Route['pathname']) {
|
||||
|
||||
@@ -75,7 +75,6 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/api/csrf': DEFAULT_TEMPLATE,
|
||||
'/api/healthz': DEFAULT_TEMPLATE,
|
||||
'/api/config': DEFAULT_TEMPLATE,
|
||||
'/api/sprite': DEFAULT_TEMPLATE,
|
||||
};
|
||||
|
||||
const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
|
||||
|
||||
@@ -72,7 +72,6 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/api/csrf': '%network_name% node API CSRF token',
|
||||
'/api/healthz': '%network_name% node API health check',
|
||||
'/api/config': '%network_name% node API app config',
|
||||
'/api/sprite': '%network_name% node API SVG sprite content',
|
||||
};
|
||||
|
||||
const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
|
||||
|
||||
@@ -70,7 +70,6 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
|
||||
'/api/csrf': 'Node API: CSRF token',
|
||||
'/api/healthz': 'Node API: Health check',
|
||||
'/api/config': 'Node API: App config',
|
||||
'/api/sprite': 'Node API: SVG sprite content',
|
||||
};
|
||||
|
||||
export default function getPageType(pathname: Route['pathname']) {
|
||||
|
||||
Vendored
-1
@@ -26,7 +26,6 @@ declare module "nextjs-routes" {
|
||||
| StaticRoute<"/api/metrics">
|
||||
| StaticRoute<"/api/monitoring/invalid-api-schema">
|
||||
| StaticRoute<"/api/proxy">
|
||||
| StaticRoute<"/api/sprite">
|
||||
| StaticRoute<"/api-docs">
|
||||
| DynamicRoute<"/apps/[id]", { "id": string }>
|
||||
| StaticRoute<"/apps">
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
+11
-13
@@ -2,9 +2,6 @@ import { Flex, Box, Tooltip, useClipboard, useColorModeValue } from '@chakra-ui/
|
||||
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';
|
||||
@@ -18,10 +15,10 @@ const formatFileSize = (fileSizeInBytes: number) => `${ (fileSizeInBytes / 1_024
|
||||
|
||||
interface IconInfo {
|
||||
name: string;
|
||||
fileSize: number;
|
||||
file_size: number;
|
||||
}
|
||||
|
||||
const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
|
||||
const Item = ({ name, file_size: fileSize, bgColor }: IconInfo & { bgColor: string }) => {
|
||||
const { hasCopied, onCopy } = useClipboard(name, 1000);
|
||||
const [ copied, setCopied ] = React.useState(false);
|
||||
|
||||
@@ -44,7 +41,7 @@ const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
|
||||
onClick={ onCopy }
|
||||
cursor="pointer"
|
||||
>
|
||||
<IconSvg name={ name as IconName } boxSize="100px" bgColor={ bgColor } borderRadius="base"/>
|
||||
<IconSvg name={ name.replace('.svg', '') 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>
|
||||
@@ -61,8 +58,7 @@ const Sprite = () => {
|
||||
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);
|
||||
return fetch<Array<IconInfo>, unknown>('/icons/registry.json');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -71,11 +67,13 @@ const Sprite = () => {
|
||||
return <ContentLoader/>;
|
||||
}
|
||||
|
||||
if (isError || !data || !('icons' in data)) {
|
||||
if (isError || !data || !Array.isArray(data)) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
const items = data.icons.filter((icon) => icon.name.includes(searchTerm));
|
||||
const items = data
|
||||
.filter((icon) => icon.name.includes(searchTerm))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
if (items.length === 0) {
|
||||
return <EmptySearchResult text="No icons found"/>;
|
||||
@@ -89,12 +87,12 @@ const Sprite = () => {
|
||||
})();
|
||||
|
||||
const total = React.useMemo(() => {
|
||||
if (!data || !('icons' in data)) {
|
||||
if (!data || !Array.isArray(data)) {
|
||||
return;
|
||||
}
|
||||
return data?.icons.reduce((result, item) => {
|
||||
return data.reduce((result, item) => {
|
||||
result.num++;
|
||||
result.fileSize += item.fileSize;
|
||||
result.fileSize += item.file_size;
|
||||
return result;
|
||||
}, { num: 0, fileSize: 0 });
|
||||
}, [ data ]);
|
||||
|
||||
Reference in New Issue
Block a user