mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01:00
Compare commits
7
Commits
2e208507c3
...
0817ba18bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0817ba18bd | ||
|
|
121a80d079 | ||
|
|
932c414b0c | ||
|
|
3a38441d42 | ||
|
|
3e039d34c2 | ||
|
|
2bae132d18 | ||
|
|
bc0004574a |
No files matched your search
+1
-1
Submodule packages/closed updated: 660413e955...52a5847368.
@@ -1,4 +1,6 @@
|
||||
using BlitzKit.Game.Models;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using BlitzKit.Game.Models;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
@@ -9,7 +11,7 @@ using SkiaSharp;
|
||||
namespace BlitzKit.Game.JSExport;
|
||||
|
||||
[JSExport]
|
||||
public class GameInterface
|
||||
public partial class GameInterface
|
||||
{
|
||||
private readonly BlitzFileProvider provider;
|
||||
|
||||
@@ -51,7 +53,7 @@ public class GameInterface
|
||||
|
||||
if (cTexture is null)
|
||||
{
|
||||
continue;
|
||||
return [];
|
||||
}
|
||||
|
||||
SKBitmap bitmap = cTexture.ToSkBitmap();
|
||||
@@ -60,10 +62,51 @@ public class GameInterface
|
||||
|
||||
return bytes;
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"T_UI_Flag_(\w+)_S")]
|
||||
private static partial Regex FlagNameRegex();
|
||||
|
||||
public byte[] Flag(string nation)
|
||||
{
|
||||
var flagsBase = "Blitz/Content/UI/Textures/Flag/TankCard/";
|
||||
|
||||
foreach (var path in provider.Files.Keys)
|
||||
{
|
||||
if (!path.StartsWith(flagsBase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||
var match = FlagNameRegex().Match(fileName);
|
||||
|
||||
if (
|
||||
match.Success
|
||||
&& match.Groups[1].Value.Equals(nation, StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
{
|
||||
if (provider.TryLoadPackageObject<UTexture2D>($"{path}.{fileName}", out var uTexture))
|
||||
{
|
||||
CTexture? cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
|
||||
|
||||
if (cTexture is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
SKBitmap bitmap = cTexture.ToSkBitmap();
|
||||
SKData data = bitmap.Encode(SKEncodedImageFormat.Webp, 80);
|
||||
byte[] bytes = data.ToArray();
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -81,5 +81,7 @@ import "@fontsource-variable/roboto-flex";
|
||||
|
||||
--duration-brief: 200ms;
|
||||
--duration-subtle: 2s;
|
||||
|
||||
--tank-card-min-width: 11rem;
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,6 @@
|
||||
}
|
||||
|
||||
&[data-light-text="true"] {
|
||||
color: var(--gray-12);
|
||||
color: var(--white-a12);
|
||||
}
|
||||
}
|
||||
@@ -11,18 +11,31 @@ const ALPHA_ONLY_COLORS = ["black", "white"] as const;
|
||||
type AlphaOnlyColor = (typeof ALPHA_ONLY_COLORS)[number];
|
||||
|
||||
const { color, alpha } = Astro.props;
|
||||
const name = `${color}${
|
||||
const nameLight = `${color}${alpha ? "A" : ""}`;
|
||||
const nameDark = `${color}${
|
||||
ALPHA_ONLY_COLORS.includes(color as AlphaOnlyColor) ? "" : "Dark"
|
||||
}${alpha ? "A" : ""}`;
|
||||
const scale = colors[name as keyof typeof colors];
|
||||
const scaleLight = colors[nameLight as keyof typeof colors];
|
||||
const scaleDark = colors[nameDark as keyof typeof colors];
|
||||
|
||||
let styles = "";
|
||||
|
||||
styles += ":root{";
|
||||
styles += ":root.dark{";
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const n = i + 1;
|
||||
const value = scale[`${color}${alpha ? "A" : ""}${n}` as keyof typeof scale];
|
||||
const value =
|
||||
scaleDark[`${color}${alpha ? "A" : ""}${n}` as keyof typeof scaleDark];
|
||||
styles += `--${color}-${alpha ? "a" : ""}${n}:${value};`;
|
||||
}
|
||||
|
||||
styles += "}";
|
||||
styles += ":root.light{";
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const n = i + 1;
|
||||
const value =
|
||||
scaleLight[`${color}${alpha ? "A" : ""}${n}` as keyof typeof scaleLight];
|
||||
styles += `--${color}-${alpha ? "a" : ""}${n}:${value};`;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,17 @@ const { locale } = Astro.props;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 1;
|
||||
background-color: var(--gray-a2);
|
||||
box-shadow: var(--shadow-none);
|
||||
backdrop-filter: var(--blur-glass) brightness(10%);
|
||||
backdrop-filter: var(--blur-glass);
|
||||
box-shadow: var(--shadow-subtle);
|
||||
background-color: var(--black-a9);
|
||||
|
||||
transition-duration: var(--duration-brief);
|
||||
|
||||
:root.light & {
|
||||
background-color: var(--white-a9);
|
||||
}
|
||||
|
||||
&[data-sticky="true"] {
|
||||
transform: translateY(-100%);
|
||||
box-shadow: var(--shadow-none);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
gap: var(--space-2);
|
||||
|
||||
overflow: hidden;
|
||||
min-width: min-content;
|
||||
@@ -10,6 +10,8 @@
|
||||
.icon-wrapper {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
|
||||
.flag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -23,7 +25,7 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 11rem;
|
||||
min-width: var(--tank-card-min-width);
|
||||
height: 7rem;
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { PopularTank } from "@protos/blitzkit/popular_tanks";
|
||||
import { api } from "../../core/api/dynamic";
|
||||
import { useAwait } from "../../hooks/useAwait";
|
||||
import { useGameStrings } from "../../hooks/useGameStrings";
|
||||
@@ -7,7 +6,12 @@ import { LinkI18n } from "../LinkI18n";
|
||||
import { Text } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export function TankCard({ id }: PopularTank) {
|
||||
interface TankCardProps {
|
||||
id: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export function TankCard({ id }: TankCardProps) {
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
const gameStrings = useGameStrings("TankEntity");
|
||||
const locale = useLocale();
|
||||
@@ -23,7 +27,7 @@ export function TankCard({ id }: PopularTank) {
|
||||
<div
|
||||
className={styles.flag}
|
||||
style={{
|
||||
backgroundImage: "url(https://i.imgur.com/QGUfXNo.png)",
|
||||
backgroundImage: `url(/api/nations/${tank.tank!.nation}.webp)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.tank-cards {
|
||||
display: grid;
|
||||
gap: var(--space-5) var(--space-4);
|
||||
grid-template-columns: repeat(
|
||||
auto-fill,
|
||||
minmax(var(--tank-card-min-width), 1fr)
|
||||
);
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TankCards({ children }: Props) {
|
||||
return <div className={styles["tank-cards"]}>{children}</div>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { classNames } from "../../core/ui/classNames";
|
||||
import type { Color } from "../../core/ui/color";
|
||||
import type { ColorWithBW } from "../../core/ui/color";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export interface TextProps extends ComponentProps<"span"> {
|
||||
@@ -8,7 +8,7 @@ export interface TextProps extends ComponentProps<"span"> {
|
||||
lowContrast?: boolean;
|
||||
size?: "regular" | "minor" | "major";
|
||||
align?: "left" | "center" | "right";
|
||||
color?: Color;
|
||||
color?: ColorWithBW;
|
||||
}
|
||||
|
||||
export function Text({
|
||||
@@ -22,10 +22,11 @@ export function Text({
|
||||
...props
|
||||
}: TextProps) {
|
||||
const scale = color === "gray" && lowContrast ? "white" : color;
|
||||
let power: string | number = 12;
|
||||
const isBW = color === "black" || color === "white";
|
||||
let power: string | number = isBW ? "a12" : 12;
|
||||
|
||||
if (lowContrast) {
|
||||
power = color === "gray" ? "a9" : 11;
|
||||
power = color === "gray" ? "a9" : isBW ? "a11" : 11;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,6 +31,10 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
|
||||
backdrop-filter: var(--blur-tint);
|
||||
|
||||
:root.light & {
|
||||
backdrop-filter: var(--blur-tint) invert(1) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
@media (width > 40rem) {
|
||||
padding: var(--space-6) var(--space-5);
|
||||
}
|
||||
@@ -50,6 +54,10 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:root.light & {
|
||||
filter: invert(1) brightness(0);
|
||||
}
|
||||
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 25%,
|
||||
|
||||
@@ -43,6 +43,10 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
:root.light & {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
|
||||
.gradient {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -58,9 +62,17 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
|
||||
backdrop-filter: blur(var(--space-6));
|
||||
|
||||
:root.light & {
|
||||
backdrop-filter: blur(var(--space-6)) brightness(1.25);
|
||||
}
|
||||
|
||||
.readable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +99,7 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
|
||||
<div class="info">
|
||||
<div class="readable">
|
||||
<Text weight="medium">{toolStrings.name}</Text>
|
||||
<Text weight="medium" color="white">{toolStrings.name}</Text>
|
||||
<Text size="minor" lowContrast>{toolStrings.slogan}</Text>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@ import type { Strings } from "@blitzkit/i18n";
|
||||
import { Avatars } from "@protos/blitzkit/avatars";
|
||||
import { PopularTanks } from "@protos/blitzkit/popular_tanks";
|
||||
import { Tank } from "@protos/blitzkit/tank";
|
||||
import { TankList } from "@protos/blitzkit/tank_list";
|
||||
import { AbstractAPI, Cache } from "./abstract";
|
||||
|
||||
const rejected = Promise.reject("Not implemented on client");
|
||||
|
||||
export class ClientAPI extends AbstractAPI {
|
||||
@UnimplementedOnClient
|
||||
@Cache()
|
||||
tankList() {
|
||||
return rejected;
|
||||
return fetchPB("/api/tanks/list.pb", TankList);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type * as colors from "@radix-ui/colors";
|
||||
|
||||
const ignoreEndings = ["A", "P3", "Dark", "DarkA", "DarkP3"] as const;
|
||||
|
||||
export type Color = Exclude<
|
||||
keyof typeof colors,
|
||||
`${string}${(typeof ignoreEndings)[number]}` | "__esModule"
|
||||
>;
|
||||
|
||||
export type ColorWithBW = Color | "black" | "white";
|
||||
@@ -16,7 +16,7 @@ interface Props {
|
||||
const { accent, noNavbar, locale } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang={locale}>
|
||||
<html lang={locale} class="dark">
|
||||
<head>
|
||||
<BaseMeta />
|
||||
|
||||
@@ -56,10 +56,23 @@ const { accent, noNavbar, locale } = Astro.props;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const root = document.documentElement;
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
function applyTheme(e: MediaQueryList | MediaQueryListEvent) {
|
||||
root.classList.toggle("dark", e.matches);
|
||||
root.classList.toggle("light", !e.matches);
|
||||
}
|
||||
|
||||
applyTheme(media);
|
||||
media.addEventListener("change", applyTheme);
|
||||
</script>
|
||||
|
||||
<slot name="head" />
|
||||
</head>
|
||||
|
||||
<body class="dark">
|
||||
<body>
|
||||
{!noNavbar && <Navbar {locale} />}
|
||||
|
||||
<slot name="pre-app" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { TankCard } from "../../../components/TankCard";
|
||||
import { TankCards } from "../../../components/TankCards";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { useAwait } from "../../../hooks/useAwait";
|
||||
import { LocaleProvider } from "../../../hooks/useLocale";
|
||||
|
||||
interface PageProps {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function Page({ locale, ...props }: PageProps) {
|
||||
return (
|
||||
<LocaleProvider locale={locale}>
|
||||
<Content {...props} />
|
||||
</LocaleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const tankList = useAwait(() => api.tankList(), "tank-list");
|
||||
|
||||
return (
|
||||
<TankCards>
|
||||
{tankList.list.slice(0, 32).map(({ id }) => (
|
||||
<TankCard key={id} id={id} />
|
||||
))}
|
||||
</TankCards>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
import "@radix-ui/colors/violet-dark.css";
|
||||
import { Section } from "../../../components/Section";
|
||||
import ToolBanner from "../../../components/ToolBanner.astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { tools } from "../../../core/tools/tools";
|
||||
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||
import { Page } from "./_index";
|
||||
|
||||
export { getStaticPaths } from "../index.astro";
|
||||
|
||||
@@ -14,16 +16,11 @@ const tankList = await api.tankList();
|
||||
<BaseLayout {locale} accent="purple">
|
||||
<ToolBanner {locale} tool={tools.tanks} slot="pre-app" />
|
||||
|
||||
<h1>tanks</h1>
|
||||
<Section>
|
||||
<Page {locale} client:load />
|
||||
</Section>
|
||||
|
||||
{
|
||||
tankList.list.map(({ id, slug }) => (
|
||||
<p>
|
||||
<a href={`/tanks/${slug}`}>
|
||||
<img height="128" width="128" src={`/api/tanks/${id}.webp`} />
|
||||
{slug} ({id})
|
||||
</a>
|
||||
</p>
|
||||
))
|
||||
}
|
||||
<!-- {
|
||||
|
||||
} -->
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Nation } from "@protos/game/proto/legacy/blitz_static_tank_component";
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import { game } from "../../../core/game/game";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
return Object.values(Nation)
|
||||
.filter(Number.isInteger)
|
||||
.map((id) => ({ params: { id } }));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
/**
|
||||
* Even Noah can't save these animals.
|
||||
*/
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const enumName = Nation[Number(params.id)];
|
||||
const nationName = enumName.replace("NATION_", "").toLowerCase();
|
||||
const flag = game.flag(nationName);
|
||||
|
||||
return new Response(new Uint8Array(flag));
|
||||
}
|
||||
Reference in new issue
Block a user