mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-14 13:16:11 +01:00
Merge branch 'main' into data-driven
This commit is contained in:
commit
5477be35e7
12 files changed
+201
-27
No files matched your search
@@ -2,7 +2,7 @@ import { alias } from "@blitzkit/core";
|
||||
import { Flex, Heading, IconButton, Table } from "@radix-ui/themes";
|
||||
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { CompareEphemeral } from "../../stores/compareEphemeral";
|
||||
import { CompareEphemeral, syncShellSlot } from "../../stores/compareEphemeral";
|
||||
import { CompareCellDirection } from "../CompareCell";
|
||||
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
|
||||
import { CompareRow } from "./CompareRow";
|
||||
@@ -50,7 +50,11 @@ export function Body({ stats }: BodyProps) {
|
||||
}}
|
||||
onClick={() => {
|
||||
CompareEphemeral.mutate((draft) => {
|
||||
draft.members[index].shell = thisShell;
|
||||
if (draft.syncShells) {
|
||||
syncShellSlot(draft.members, shellIndex);
|
||||
} else {
|
||||
draft.members[index].shell = thisShell;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button, Dialog, Flex, SegmentedControl } from "@radix-ui/themes";
|
||||
import { api } from "../../api/dynamic";
|
||||
import { tankToCompareMember } from "../../core/blitzkit/tankToCompareMember";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { CompareEphemeral } from "../../stores/compareEphemeral";
|
||||
import { CompareEphemeral, syncShellSlot } from "../../stores/compareEphemeral";
|
||||
import {
|
||||
ComparePersistent,
|
||||
type DeltaMode,
|
||||
@@ -22,6 +22,7 @@ export function Controls({
|
||||
onAddTankDialogOpenChange,
|
||||
}: ControlsProps) {
|
||||
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
|
||||
const syncShells = CompareEphemeral.use((state) => state.syncShells);
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
@@ -36,11 +37,11 @@ export function Controls({
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Dialog.Content maxHeight={DIALOG_MAX_HEIGHT}>
|
||||
<Dialog.Title align="center">
|
||||
{strings.website.tools.compare.actions.add.title}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description align="center">
|
||||
<Dialog.Description align="center" mb="4">
|
||||
{strings.website.tools.compare.actions.add.description}
|
||||
</Dialog.Description>
|
||||
|
||||
@@ -111,6 +112,31 @@ export function Controls({
|
||||
{strings.website.tools.compare.actions.deltas.absolute}
|
||||
</SegmentedControl.Item>
|
||||
</SegmentedControl.Root>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
gap="2"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
CompareEphemeral.mutate((draft) => {
|
||||
draft.syncShells = !draft.syncShells;
|
||||
|
||||
if (!draft.syncShells || draft.members.length === 0) return;
|
||||
|
||||
const [first] = draft.members;
|
||||
|
||||
syncShellSlot(
|
||||
draft.members,
|
||||
first.gun.shells.findIndex(({ id }) => id === first.shell.id),
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Checkbox variant="classic" checked={syncShells} />
|
||||
<Text size="2">
|
||||
{strings.website.tools.compare.actions.sync_shells}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export function TankControl({ index, slug }: TankControlProps) {
|
||||
</IconButton>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Dialog.Content maxHeight={DIALOG_MAX_HEIGHT}>
|
||||
<Dialog.Title align="center">
|
||||
{strings.website.common.tank_search.swap_dialog_title}
|
||||
</Dialog.Title>
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useEffect, useState, type ReactNode } from "react";
|
||||
import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps";
|
||||
import { InlineSkeleton } from "./InlineSkeleton";
|
||||
|
||||
const TIME = 1792598400 * 1000;
|
||||
const TIME = 1793898000 * 1000;
|
||||
const HREF = "https://preview.blitzkit.app/tanks";
|
||||
|
||||
export function Countdown({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const [timeLeft, setTimeLeft] = useState<number>(TIME - Date.now());
|
||||
@@ -31,7 +32,7 @@ export function Countdown({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
string = (
|
||||
<>
|
||||
revealed in{" "}
|
||||
in{" "}
|
||||
<Code highContrast>
|
||||
{hours}h {minutes}m {seconds}s
|
||||
</Code>
|
||||
@@ -47,7 +48,7 @@ export function Countdown({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
color="gray"
|
||||
highContrast
|
||||
style={{ display: "content" }}
|
||||
href="https://preview.blitzkit.app/tanks"
|
||||
href={timeLeft <= 0 ? HREF : undefined}
|
||||
underline="none"
|
||||
>
|
||||
<Flex
|
||||
@@ -75,11 +76,11 @@ export function Countdown({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
<Flex direction="column">
|
||||
<Heading size={{ initial: "4", sm: "6" }}>
|
||||
Tier XIs {!skeleton && string}{" "}
|
||||
More Tier XIs {!skeleton && string}{" "}
|
||||
{skeleton && <InlineSkeleton width="10rem" />}
|
||||
</Heading>
|
||||
<Text size={{ initial: "2", sm: "3" }}>
|
||||
Get ready to go to the Tankopedia!
|
||||
Catch the next 3 tanks on BlitzKit Preview
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { memo, useEffect, useMemo, useRef } from "react";
|
||||
|
||||
const RESOLUTION = 1000;
|
||||
const HEIGHT = 8;
|
||||
const DENSE_TICKS = 100;
|
||||
|
||||
interface StatScaleProps {
|
||||
values: number[];
|
||||
value: number;
|
||||
lowerIsBetter?: boolean;
|
||||
}
|
||||
|
||||
export const StatScale = memo<StatScaleProps>(
|
||||
({ values, value, lowerIsBetter }) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const ticks = useMemo(() => {
|
||||
const min = Math.min(value, ...values);
|
||||
const max = Math.max(value, ...values);
|
||||
const range = max - min;
|
||||
const positions = new Set<number>();
|
||||
|
||||
for (const other of values) {
|
||||
const raw = range === 0 ? 0.5 : (other - min) / range;
|
||||
const x = Math.round((lowerIsBetter ? 1 - raw : raw) * RESOLUTION);
|
||||
|
||||
if (x === 0 || x === RESOLUTION) continue;
|
||||
|
||||
positions.add(x);
|
||||
}
|
||||
|
||||
return Array.from(positions, (x) => ({ x }));
|
||||
}, [values, value, lowerIsBetter]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const draw = () => {
|
||||
const { width } = canvas.getBoundingClientRect();
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const canvasWidth = Math.ceil(width * pixelRatio);
|
||||
const canvasHeight = Math.ceil(HEIGHT * pixelRatio);
|
||||
|
||||
if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
|
||||
canvas.width = canvasWidth;
|
||||
canvas.height = canvasHeight;
|
||||
}
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
|
||||
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
context.clearRect(0, 0, width, HEIGHT);
|
||||
|
||||
const styles = getComputedStyle(canvas);
|
||||
for (const { x } of ticks) {
|
||||
context.fillStyle = styles.getPropertyValue("--gray-a6").trim();
|
||||
context.fillRect((x / RESOLUTION) * width, 0, 1, HEIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(draw);
|
||||
observer.observe(canvas);
|
||||
window.addEventListener("resize", draw);
|
||||
draw();
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
window.removeEventListener("resize", draw);
|
||||
};
|
||||
}, [ticks]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: HEIGHT,
|
||||
margin: "auto 0",
|
||||
opacity: Math.min(1, Math.sqrt(DENSE_TICKS / ticks.length)),
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user