This commit is contained in:
tresabhi committed 2026-08-01 14:55:57 -05:00
1 parent ed92440b94
commit f85d097c16
44 files changed
+5 -5

No files matched your search

@@ -0,0 +1,288 @@
import {
CodeIcon,
HeightIcon,
ImageIcon,
ResetIcon,
TimerIcon,
WidthIcon,
} from "@radix-ui/react-icons";
import { Box, Button, Flex, Heading, ScrollArea, Text } from "@radix-ui/themes";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { CopyButton } from "../../../../components/CopyButton";
import { Boolean } from "../../../../components/Embeds/Boolean";
import { Color } from "../../../../components/Embeds/Color";
import { Enum } from "../../../../components/Embeds/Enum";
import { GenerateURL } from "../../../../components/Embeds/GenerateURL";
import { Import } from "../../../../components/Embeds/Import";
import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper";
import { Radius } from "../../../../components/Embeds/Radius";
import { RichText } from "../../../../components/Embeds/RichText";
import { Size } from "../../../../components/Embeds/Size";
import { SizeWithout0 } from "../../../../components/Embeds/SizeWithout0";
import { Slider } from "../../../../components/Embeds/Slider";
import { TextController } from "../../../../components/Embeds/TextController";
import { PageWrapper } from "../../../../components/PageWrapper";
import {
embedConfigurations,
extractEmbedConfigDefaults,
} from "../../../../constants/embeds";
import { NAVBAR_HEIGHT } from "../../../../constants/navbar";
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from "../../../../hooks/useLocale";
import { EmbedState, type EmbedConfig } from "../../../../stores/embedState";
import { EmbedItemType } from "../../../../stores/embedState/constants";
export interface EmbedPreviewControllerProps {
configKey: string;
}
interface PageProps {
embed: keyof typeof embedConfigurations;
}
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
const config = embedConfigurations[embed];
EmbedState.useInitialization(extractEmbedConfigDefaults(config));
return (
<LocaleProvider locale={locale}>
<Content embed={embed} />
</LocaleProvider>
);
}
function Content({ embed }: PageProps) {
const config = embedConfigurations[embed] as EmbedConfig;
const [backgroundImage, setBackgroundImage] = useState(
"/assets/images/backgrounds/embed-default.webp"
);
const fileInput = useRef<HTMLInputElement>(null!);
const { strings } = useLocale();
useEffect(() => {
fileInput.current = document.createElement("input");
fileInput.current.type = "file";
fileInput.current.style.display = "none";
fileInput.current.accept = "image/*";
fileInput.current.onchange = (event) => {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
setBackgroundImage(URL.createObjectURL(file));
};
}, []);
return (
<PageWrapper padding="0" maxWidth="104rem" color="red">
<Flex>
<ScrollArea
scrollbars="vertical"
style={{ height: "100%", maxWidth: 320 }}
>
<Flex direction="column" gap="2" p="4">
<Import />
<Heading>
{strings.website.tools.embed.configuration.export.title}
</Heading>
<Text size="2" color="gray" mb="2">
{strings.website.tools.embed.configuration.export.description}
</Text>
<Flex mb="6" gap="2" wrap="wrap">
<GenerateURL embed={embed} />
<CopyButton
variant="outline"
copy={() =>
"body { margin: 0; background-color: transparent; overflow: hidden; }"
}
>
<CodeIcon />
{strings.website.tools.embed.configuration.export.css}
</CopyButton>
<CopyButton
variant="outline"
copy={() => `${EmbedState.state.width}`}
>
<WidthIcon />
{strings.website.tools.embed.configuration.export.width}
</CopyButton>
<CopyButton
variant="outline"
copy={() => `${EmbedState.state.height}`}
>
<HeightIcon />
{strings.website.tools.embed.configuration.export.height}
</CopyButton>
<CopyButton variant="outline" copy={() => "1"}>
<TimerIcon />
{strings.website.tools.embed.configuration.export.framerate}
</CopyButton>
</Flex>
<Flex justify="between" align="center">
<Heading>
{strings.website.tools.embed.configuration.customize.title}
</Heading>
<Button
color="red"
variant="ghost"
onClick={() => {
EmbedState.set(EmbedState.initial);
}}
>
<ResetIcon />{" "}
{strings.website.tools.embed.configuration.customize.reset}
</Button>
</Flex>
<Text size="2" color="gray" mb="2">
{strings.website.tools.embed.configuration.customize.description}
</Text>
{Object.keys(config).map((configKey) => {
const item = config[configKey];
let control: ReactNode;
let oneLiner = false;
switch (item.type) {
case EmbedItemType.Boolean: {
control = <Boolean configKey={configKey} />;
oneLiner = true;
break;
}
case EmbedItemType.Color: {
control = <Color configKey={configKey} />;
break;
}
case EmbedItemType.Radius: {
control = <Radius configKey={configKey} />;
oneLiner = true;
break;
}
case EmbedItemType.Size: {
control = <Size configKey={configKey} />;
oneLiner = true;
break;
}
case EmbedItemType.SizeWithout0: {
control = <SizeWithout0 configKey={configKey} />;
oneLiner = true;
break;
}
case EmbedItemType.Slider: {
control = <Slider configKey={configKey} config={item} />;
oneLiner = true;
break;
}
case EmbedItemType.String: {
control = <TextController configKey={configKey} />;
break;
}
case EmbedItemType.RichText: {
control = <RichText configKey={configKey} />;
break;
}
case EmbedItemType.Enum: {
control = <Enum configKey={configKey} config={item} />;
oneLiner = true;
break;
}
}
return (
<Flex
key={configKey}
direction={oneLiner ? undefined : "column"}
gap={oneLiner ? "2" : "1"}
justify={oneLiner ? "between" : undefined}
mb={oneLiner ? "2" : "4"}
pb={item.pad ? "6" : undefined}
>
<Text>
{
(
strings.website.tools.embed.types[embed]
.options as Record<string, string>
)[configKey]
}
</Text>
<Flex
gap="2"
align="center"
flexGrow="1"
justify={oneLiner ? "end" : undefined}
>
{control}
</Flex>
</Flex>
);
})}
</Flex>
</ScrollArea>
<Box
p="4"
flexGrow="1"
position="sticky"
top={`${NAVBAR_HEIGHT}px`}
height={`calc(100vh - ${NAVBAR_HEIGHT}px)`}
>
<Flex
direction="column"
height="100%"
style={{
borderRadius: "var(--radius-3)",
overflow: "hidden",
boxShadow: "var(--shadow-3)",
}}
>
<Flex
justify="between"
align="center"
p="2"
pl="3"
style={{
backgroundColor: "var(--color-panel-solid)",
}}
>
<Text color="gray">
{strings.website.tools.embed.preview.title}
</Text>
<Button
variant="outline"
color="gray"
onClick={() => fileInput.current?.click()}
>
<ImageIcon /> {strings.website.tools.embed.preview.upload}
</Button>
</Flex>
<Box
flexGrow="1"
style={{
background: `url(${backgroundImage}) center / cover no-repeat`,
}}
position="relative"
overflow="hidden"
>
<PreviewWrapper name={embed} />
</Box>
</Flex>
</Box>
</Flex>
</PageWrapper>
);
}
@@ -0,0 +1,60 @@
import { Box } from "@radix-ui/themes";
import { useMemo } from "react";
import { parse } from "urlon";
import { BlitzKitTheme } from "../../../../../components/BlitzKitTheme";
import {
embedConfigurations,
embedRenderers,
extractEmbedConfigDefaults,
} from "../../../../../constants/embeds";
import {
LocaleProvider,
type LocaleAcceptorProps,
} from "../../../../../hooks/useLocale";
import {
EmbedState,
type EmbedConfig,
type ExtractEmbedConfigTypes,
} from "../../../../../stores/embedState";
import { useEmbedStateCurry } from "../../../../../stores/embedState/utilities";
interface PageProps {
embed: keyof typeof embedConfigurations;
}
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
const state = useMemo(() => {
const params = new URLSearchParams(window.location.search);
const stateRaw = params.get("state");
const partialState = stateRaw === null ? {} : parse(stateRaw);
const state = {
...extractEmbedConfigDefaults(embedConfigurations[embed]),
...partialState,
} as ExtractEmbedConfigTypes<EmbedConfig>;
return state;
}, [embed]);
EmbedState.useInitialization(state);
return (
<LocaleProvider locale={locale}>
<Content embed={embed} />
</LocaleProvider>
);
}
function Content({ embed }: PageProps) {
const Renderer = embedRenderers[embed];
const { useEmbedState } = useEmbedStateCurry();
const width = useEmbedState("width");
const height = useEmbedState("height");
return (
<BlitzKitTheme style={{ background: "transparent" }}>
<Box width={`${width}px`} height={`${height}px`} overflow="hidden">
<Renderer />
</Box>
</BlitzKitTheme>
);
}
@@ -0,0 +1,13 @@
---
import EmbedLayout from "../../../../../layouts/EmbedLayout.astro";
import type { getStaticPaths } from "../index.astro";
import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
const { locale } = Astro.props;
---
<EmbedLayout>
<Page {locale} client:only="react" embed={Astro.params.embed} />
</EmbedLayout>
@@ -0,0 +1,65 @@
---
import { AstroSeo } from "@astrolib/seo";
import type { GetStaticPaths } from "astro";
import { capitalize } from "lodash-es";
import { embedConfigurations } from "../../../../constants/embeds";
import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { getStaticPaths as parentGetStaticPaths } from "../index.astro";
import { Page } from "./_index";
export const getStaticPaths = (() => {
const parentPaths = parentGetStaticPaths();
return Object.keys(embedConfigurations)
.map((embed) =>
parentPaths.map((parent) => ({
params: {
...parent.params,
embed: embed as keyof typeof embedConfigurations,
},
}))
)
.flat();
}) satisfies GetStaticPaths;
const name = capitalize(Astro.params.embed);
const title = `BlitzKit - ${name} embed`;
const description = `BlitzKit ${name} embeds for streaming World of Tanks Blitz with extreme customization`;
const { locale } = Astro.props;
---
<AstroSeo
title={title}
description={description}
canonical={Astro.url.href}
openGraph={{
url: Astro.url.href,
title,
description,
site_name: "BlitzKit",
}}
twitter={{ cardType: "summary_large_image" }}
/>
<style is:global>
@keyframes fade-out-border {
from {
outline: 1rem solid #ff0000ff;
}
to {
outline: 0.75rem solid #ff000000;
}
}
.preview-container-animated {
animation: fade-out-border 0.5s linear;
}
.preview-container-animated:hover {
outline: 1rem solid #ff000080;
}
</style>
<BaseLayout {locale}>
<Page {locale} client:load embed={Astro.params.embed} />
</BaseLayout>
@@ -0,0 +1,92 @@
import { Box, Flex, Link, Text } from "@radix-ui/themes";
import { PreviewWrapper } from "../../../components/Embeds/PreviewWrapper";
import { LinkI18n } from "../../../components/LinkI18n";
import { PageWrapper } from "../../../components/PageWrapper";
import { embedConfigurations } from "../../../constants/embeds";
import { Var } from "../../../core/radix/var";
import {
type LocaleAcceptorProps,
LocaleProvider,
useLocale,
} from "../../../hooks/useLocale";
export function Page({ locale }: LocaleAcceptorProps) {
return (
<LocaleProvider locale={locale}>
<Content />
</LocaleProvider>
);
}
function Content() {
const { locale, strings } = useLocale();
return (
<PageWrapper color="red">
<Text align="center" mt="3">
{strings.website.tools.embed.coming_soon}{" "}
<Link
underline="always"
href="https://discord.gg/nDt7AjGJQH"
target="_blank"
>
{strings.website.tools.embed.suggest}
</Link>
</Text>
<Flex justify="center">
{Object.keys(embedConfigurations).map((embed) => (
<LinkI18n
locale={locale}
href={`/embed/${embed}`}
color="gray"
highContrast
style={{
width: "fit-content",
height: "fit-content",
}}
underline="hover"
>
<Flex
direction="column"
overflow="hidden"
style={{
borderRadius: Var("radius-4"),
backgroundColor: Var("color-panel-solid"),
boxShadow: Var("shadow-3"),
}}
>
<Flex justify="center" p="2">
<Text>
{
strings.website.tools.embed.types[
embed as keyof typeof strings.website.tools.embed.types
].name
}
</Text>
</Flex>
<Box
width="20rem"
height="20rem"
overflow="hidden"
position="relative"
style={{
backgroundImage:
"url(/assets/images/backgrounds/embed-default.webp)",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<PreviewWrapper
naked
name={embed as keyof typeof embedConfigurations}
/>
</Box>
</Flex>
</LinkI18n>
))}
</Flex>
</PageWrapper>
);
}
@@ -0,0 +1,13 @@
---
import { tools } from "../../../constants/tools";
import ToolLayout from "../../../layouts/ToolLayout.astro";
import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
const { locale } = Astro.props;
---
<ToolLayout {locale} tool={tools.embed}>
<Page {locale} />
</ToolLayout>