diff --git a/packages/core/src/protos/gallery.proto b/packages/core/src/protos/gallery.proto index 7adf88b53..85e1199dc 100644 --- a/packages/core/src/protos/gallery.proto +++ b/packages/core/src/protos/gallery.proto @@ -11,4 +11,5 @@ message Gallery { message Avatar { required string id = 1; required I18nString name = 2; + required string extension = 3; } \ No newline at end of file diff --git a/packages/core/src/protos/gallery.ts b/packages/core/src/protos/gallery.ts index aef37981e..1771d299a 100644 --- a/packages/core/src/protos/gallery.ts +++ b/packages/core/src/protos/gallery.ts @@ -5,10 +5,10 @@ // source: packages/core/src/protos/gallery.proto /* eslint-disable */ -import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire'; +import { I18nString, createBaseI18nString } from './i18n'; -export const protobufPackage = "blitzkit"; +export const protobufPackage = 'blitzkit'; export interface Gallery { avatars: Avatar[]; @@ -17,6 +17,7 @@ export interface Gallery { export interface Avatar { id: string; name: I18nString; + extension: string; } function createBaseGallery(): Gallery { @@ -24,7 +25,10 @@ function createBaseGallery(): Gallery { } export const Gallery: MessageFns = { - encode(message: Gallery, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + encode( + message: Gallery, + writer: BinaryWriter = new BinaryWriter(), + ): BinaryWriter { for (const v of message.avatars) { Avatar.encode(v!, writer.uint32(10).fork()).join(); } @@ -32,7 +36,8 @@ export const Gallery: MessageFns = { }, decode(input: BinaryReader | Uint8Array, length?: number): Gallery { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; const message = createBaseGallery(); while (reader.pos < end) { @@ -57,7 +62,9 @@ export const Gallery: MessageFns = { fromJSON(object: any): Gallery { return { - avatars: globalThis.Array.isArray(object?.avatars) ? object.avatars.map((e: any) => Avatar.fromJSON(e)) : [], + avatars: globalThis.Array.isArray(object?.avatars) + ? object.avatars.map((e: any) => Avatar.fromJSON(e)) + : [], }; }, @@ -80,22 +87,29 @@ export const Gallery: MessageFns = { }; function createBaseAvatar(): Avatar { - return { id: "", name: createBaseI18nString() }; + return { id: '', name: createBaseI18nString(), extension: '' }; } export const Avatar: MessageFns = { - encode(message: Avatar, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.id !== "") { + encode( + message: Avatar, + writer: BinaryWriter = new BinaryWriter(), + ): BinaryWriter { + if (message.id !== '') { writer.uint32(10).string(message.id); } if (message.name !== undefined) { I18nString.encode(message.name, writer.uint32(18).fork()).join(); } + if (message.extension !== '') { + writer.uint32(26).string(message.extension); + } return writer; }, decode(input: BinaryReader | Uint8Array, length?: number): Avatar { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const reader = + input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; const message = createBaseAvatar(); while (reader.pos < end) { @@ -117,6 +131,14 @@ export const Avatar: MessageFns = { message.name = I18nString.decode(reader, reader.uint32()); continue; } + case 3: { + if (tag !== 26) { + break; + } + + message.extension = reader.string(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -128,19 +150,25 @@ export const Avatar: MessageFns = { fromJSON(object: any): Avatar { return { - id: globalThis.String(assertSet("Avatar.id", object.id)), - name: I18nString.fromJSON(assertSet("Avatar.name", object.name)), + id: globalThis.String(assertSet('Avatar.id', object.id)), + name: I18nString.fromJSON(assertSet('Avatar.name', object.name)), + extension: globalThis.String( + assertSet('Avatar.extension', object.extension), + ), }; }, toJSON(message: Avatar): unknown { const obj: any = {}; - if (message.id !== "") { + if (message.id !== '') { obj.id = message.id; } if (message.name !== undefined) { obj.name = I18nString.toJSON(message.name); } + if (message.extension !== '') { + obj.extension = message.extension; + } return obj; }, @@ -149,26 +177,43 @@ export const Avatar: MessageFns = { }, fromPartial, I>>(object: I): Avatar { const message = createBaseAvatar(); - message.id = object.id ?? ""; - message.name = (object.name !== undefined && object.name !== null) - ? I18nString.fromPartial(object.name) - : createBaseI18nString(); + message.id = object.id ?? ''; + message.name = + object.name !== undefined && object.name !== null + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); + message.extension = object.extension ?? ''; return message; }, }; -type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; -export type DeepPartial = T extends Builtin ? T - : T extends globalThis.Array ? globalThis.Array> - : T extends ReadonlyArray ? ReadonlyArray> - : T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial } - : T extends {} ? { [K in keyof T]?: DeepPartial } - : Partial; +export type DeepPartial = T extends Builtin + ? T + : T extends globalThis.Array + ? globalThis.Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends { $case: string; value: unknown } + ? { $case: T['$case']; value?: DeepPartial } + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; type KeysOfUnion = T extends T ? keyof T : never; -export type Exact = P extends Builtin ? P - : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; function isSet(value: any): boolean { return value !== null && value !== undefined; diff --git a/packages/scripts/src/buildAssets/glossary.ts b/packages/scripts/src/buildAssets/glossary.ts index 282217734..4b50d7ff6 100644 --- a/packages/scripts/src/buildAssets/glossary.ts +++ b/packages/scripts/src/buildAssets/glossary.ts @@ -33,11 +33,14 @@ export async function glossary() { avatars[key].avatar.name.locales[supported.locale] = glossaryEntry.title; } else { + const extension = extname(glossaryEntry.image_url); + avatars[key] = { url: glossaryEntry.image_url, avatar: { id: key, name: { locales: { [supported.locale]: glossaryEntry.title } }, + extension, }, }; } @@ -65,13 +68,12 @@ export async function glossary() { gallery.avatars.push(avatar); - const fileExtension = extname(url); const content = await fetch(url).then((response) => response.arrayBuffer(), ); changes.push({ - path: `gallery/avatars/${key}${fileExtension}`, + path: `gallery/avatars/${key}${avatar.extension}`, content: Buffer.from(content), }); diff --git a/packages/scripts/src/core/github/commitAssets.ts b/packages/scripts/src/core/github/commitAssets.ts index de1bc699e..0d8a652f7 100644 --- a/packages/scripts/src/core/github/commitAssets.ts +++ b/packages/scripts/src/core/github/commitAssets.ts @@ -7,7 +7,7 @@ export async function commitAssets(message: string, changes: FileChange[]) { if (changes.length === 0) return; - const chunks = chunk(changes, 16); + const chunks = chunk(changes, 64); let chunkIndex = 0; for (const chunk of chunks) { diff --git a/packages/scripts/src/core/github/commitMultipleFiles.ts b/packages/scripts/src/core/github/commitMultipleFiles.ts index ac826f778..364ac6b0c 100644 --- a/packages/scripts/src/core/github/commitMultipleFiles.ts +++ b/packages/scripts/src/core/github/commitMultipleFiles.ts @@ -38,7 +38,11 @@ export async function commitMultipleFiles( ); changes.push(change); + + return; } + + console.log(`🔵 (${buffer.length.toLocaleString()}B) ${blobPath}`); } else { throw new Error( `Unexpected status code ${response.status} for ${blobPath}`, diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx index 0c72d6e09..629de2051 100644 --- a/packages/website/src/components/Gallery/Card.tsx +++ b/packages/website/src/components/Gallery/Card.tsx @@ -1,3 +1,4 @@ +import { asset, Avatar } from '@blitzkit/core'; import { Cross1Icon, DownloadIcon } from '@radix-ui/react-icons'; import { Dialog, @@ -9,16 +10,19 @@ import { } from '@radix-ui/themes'; import { useRef } from 'react'; import { useIntersection } from '../../hooks/useIntersection'; +import { useLocale } from '../../hooks/useLocale'; import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; type GalleryCardProps = MaybeSkeletonComponentProps<{ - id: string; - name: string; + avatar: Avatar; }>; export function GalleryCard(props: GalleryCardProps) { - const src = `/api/gallery/${!props.skeleton && props.id}.webp`; + const src = asset( + `gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`, + ); const card = useRef(null); + const { unwrap } = useLocale(); useIntersection(() => props.skeleton && props.onIntersection?.(), card, { disabled: !props.skeleton || props.onIntersection === undefined, @@ -50,11 +54,11 @@ export function GalleryCard(props: GalleryCardProps) { objectFit: 'cover', }} src={src} - alt={props.name} + alt={unwrap(props.avatar.name)} /> - {props.name} + {unwrap(props.avatar.name)} )} @@ -86,12 +90,12 @@ export function GalleryCard(props: GalleryCardProps) { - {props.name} + {unwrap(props.avatar.name)} - {props.name} + {unwrap(props.avatar.name)} diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx index 64d66b299..f2c112f9f 100644 --- a/packages/website/src/components/Gallery/List.tsx +++ b/packages/website/src/components/Gallery/List.tsx @@ -7,12 +7,10 @@ import { useMemo, useState } from 'react'; import { awaitableGallery } from '../../core/awaitables/gallery'; import { useLocale } from '../../hooks/useLocale'; import { GalleryEphemeral } from '../../stores/galleryEphemeral'; +import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; +import { InlineSkeleton } from '../InlineSkeleton'; import { GalleryCard } from './Card'; -interface GalleryListProps { - avatars: Avatar[]; -} - export interface Avatar { name: string; id: string; @@ -23,7 +21,7 @@ const gallery = await awaitableGallery; const DEFAULT_LOADED = 42; const PREVIEW_COUNT = 28; -export function GalleryList() { +export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { const search = GalleryEphemeral.use((state) => state.search); const [loadedCards, setLoadedCards] = useState(DEFAULT_LOADED); const { locale, strings, unwrap } = useLocale(); @@ -49,22 +47,25 @@ export function GalleryList() { return ( <> - {literals(strings.website.tools.gallery.search.results, [ - filtered.length.toLocaleString(locale), - ])} + {skeleton ? ( + + ) : ( + literals(strings.website.tools.gallery.search.results, [ + filtered.length.toLocaleString(locale), + ]) + )} - {filtered.slice(0, loadedCards).map((avatar) => ( - - ))} + {!skeleton && + filtered + .slice(0, loadedCards) + .map((avatar) => )} {times( - Math.min(PREVIEW_COUNT, filtered.length - loadedCards), + skeleton + ? DEFAULT_LOADED + PREVIEW_COUNT + : Math.min(PREVIEW_COUNT, filtered.length - loadedCards), (index) => ( (null); const mutateGalleryEphemeral = GalleryEphemeral.useMutation(); const { strings } = useLocale(); return ( { diff --git a/packages/website/src/pages/[...locale]/tools/gallery/_index.tsx b/packages/website/src/pages/[...locale]/tools/gallery/_index.tsx index 8e5f04f09..4357f5fc4 100644 --- a/packages/website/src/pages/[...locale]/tools/gallery/_index.tsx +++ b/packages/website/src/pages/[...locale]/tools/gallery/_index.tsx @@ -1,16 +1,17 @@ import { GalleryList } from '../../../../components/Gallery/List'; import { GallerySearch } from '../../../../components/Gallery/Search'; import { PageWrapper } from '../../../../components/PageWrapper'; -import { awaitableGallery } from '../../../../core/awaitables/gallery'; import { LocaleProvider, - useLocale, type LocaleAcceptorProps, } from '../../../../hooks/useLocale'; import { GalleryEphemeral } from '../../../../stores/galleryEphemeral'; +import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps'; - -export function Page({ locale, ...props }: LocaleAcceptorProps) { +export function Page({ + locale, + ...props +}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { return ( @@ -18,13 +19,12 @@ export function Page({ locale, ...props }: LocaleAcceptorProps) { ); } -function Content() { - +function Content({ skeleton }: MaybeSkeletonComponentProps) { return ( - - + + ); diff --git a/packages/website/src/pages/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...locale]/tools/gallery/index.astro index 2fb84640c..048296a51 100644 --- a/packages/website/src/pages/[...locale]/tools/gallery/index.astro +++ b/packages/website/src/pages/[...locale]/tools/gallery/index.astro @@ -1,8 +1,6 @@ --- -import type { Avatar } from '../../../../components/Gallery/List'; import { galleryTool } from '../../../../constants/tools'; -import { awaitableGallery } from '../../../../core/awaitables/gallery'; - import ToolLayout from '../../../../layouts/ToolLayout.astro'; +import ToolLayout from '../../../../layouts/ToolLayout.astro'; import { Page } from './_index'; export { getStaticPaths } from '../../index.astro'; @@ -11,5 +9,7 @@ const { locale } = Astro.params; --- - + + + diff --git a/packages/website/src/pages/api/gallery/[avatar].webp.ts b/packages/website/src/pages/api/gallery/[avatar].webp.ts deleted file mode 100644 index 08dab9670..000000000 --- a/packages/website/src/pages/api/gallery/[avatar].webp.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DEFAULT_LOCALE } from '@blitzkit/i18n'; -import type { APIRoute, GetStaticPaths } from 'astro'; -import { fetchGlossary } from '../../../core/blitz/fetchGlossary'; - -export const getStaticPaths = (async () => { - const glossary = await fetchGlossary(DEFAULT_LOCALE); - const avatarKeys = Object.keys(glossary) - .filter((key) => key.startsWith('avatar_')) - .map((key) => key.replace('avatar_', '')); - - return avatarKeys.map((avatar) => ({ params: { avatar } })); -}) satisfies GetStaticPaths; - -const glossary = await fetchGlossary(DEFAULT_LOCALE); - -export const GET: APIRoute<{}, { avatar: string }> = async ({ params }) => { - const image = - glossary[`avatar_${params.avatar}`].image_url ?? - 'https://i.imgur.com/uXBiK05.jpeg'; - - if (import.meta.env.MODE === 'development') { - return Response.redirect(image); - } else { - const response = await fetch(image); - const blob = await response.blob(); - return new Response(blob, { headers: response.headers }); - } -};