Compare commits

..
4 Commits
Author SHA1 Message Date
TrèsAbhi 2e208507c3 feat: simplify scrollbar 2026-04-20 21:01:54 -05:00
TrèsAbhi 33209a6d31 fix: tank names 2026-04-20 20:55:24 -05:00
TrèsAbhi 13311245a5 feat: dlcs 2026-04-20 20:51:57 -05:00
TrèsAbhi 4067357b54 fix: home-page popular tank overflow 2026-04-20 20:44:45 -05:00
5 changed files with 29 additions and 143 deletions

No files matched your search

@@ -8,43 +8,13 @@
.content {
overflow: auto;
scrollbar-width: none;
padding-bottom: var(--space-4);
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
transition: scrollbar-color var(--duration-brief);
scrollbar-color: transparent transparent;
.bar-wrapper {
opacity: 0;
padding: var(--space-1);
background-color: var(--gray-3);
transition: opacity var(--duration-brief);
border-radius: var(--max-size);
.bar-container {
height: var(--space-3);
position: relative;
.bar {
height: 100%;
min-width: min(100%, var(--space-6));
position: absolute;
border-radius: var(--max-size);
background-color: var(--gray-7);
cursor: grab;
&:active {
cursor: grabbing;
}
}
&:hover {
scrollbar-color: var(--gray-7) var(--gray-3);
}
}
@@ -6,36 +6,11 @@ import styles from "./index.module.css";
interface ScrollerProps extends ComponentProps<"div"> {}
export function Scroller({ children, className, ...props }: ScrollerProps) {
const bar = useRef<HTMLDivElement>(null!);
const container = useRef<HTMLDivElement>(null!);
const content = useRef<HTMLDivElement>(null!);
const curtainLeft = useRef<HTMLDivElement>(null!);
const curtainRight = useRef<HTMLDivElement>(null!);
useEffect(() => {
function updateBarSize() {
const visible = content.current.clientWidth;
const total = content.current.scrollWidth;
if (total <= 0) return;
bar.current.style.width = `${(visible / total) * 100}%`;
}
function syncBar() {
const maxTravel = container.current.clientWidth - bar.current.clientWidth;
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
if (maxScroll <= 0 || maxTravel <= 0) {
bar.current.style.left = "0";
return;
}
const ratio = content.current.scrollLeft / maxScroll;
bar.current.style.left = `${ratio * maxTravel}px`;
}
function syncCurtains() {
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
@@ -61,61 +36,20 @@ export function Scroller({ children, className, ...props }: ScrollerProps) {
curtainRight.current.style.opacity = `${rightOpacity}`;
}
let lastX = 0;
function handlePointerDown(event: PointerEvent) {
lastX = event.clientX;
bar.current.setPointerCapture(event.pointerId);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const dx = event.clientX - lastX;
lastX = event.clientX;
const maxTravel = container.current.clientWidth - bar.current.clientWidth;
if (maxTravel <= 0) return;
const maxScroll =
content.current.scrollWidth - content.current.clientWidth;
const delta = (dx / maxTravel) * maxScroll;
content.current.scrollLeft += delta;
syncBar();
}
function handlePointerUp(event: PointerEvent) {
bar.current.releasePointerCapture(event.pointerId);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
function handleScroll() {
syncBar();
syncCurtains();
}
function handleResize() {
updateBarSize();
syncBar();
syncCurtains();
}
updateBarSize();
syncBar();
syncCurtains();
bar.current.addEventListener("pointerdown", handlePointerDown);
content.current.addEventListener("scroll", syncBar);
window.addEventListener("resize", handleResize);
content.current.addEventListener("scroll", handleScroll);
return () => {
bar.current.removeEventListener("pointerdown", handlePointerDown);
content.current.removeEventListener("scroll", syncBar);
window.removeEventListener("resize", handleResize);
content.current.removeEventListener("scroll", handleScroll);
};
@@ -127,12 +61,6 @@ export function Scroller({ children, className, ...props }: ScrollerProps) {
{children}
</div>
<div className={styles["bar-wrapper"]}>
<div className={styles["bar-container"]} ref={container}>
<div className={styles.bar} ref={bar} />
</div>
</div>
<div ref={curtainLeft} data-side="left" className={styles.curtain} />
<div ref={curtainRight} data-side="right" className={styles.curtain} />
</div>
@@ -1,11 +1,12 @@
.section {
display: flex;
justify-content: center;
padding: var(--space-5);
padding: var(--space-4);
.content {
flex: 1;
max-width: var(--page-width);
overflow: hidden;
display: flex;
flex-direction: column;
+21 -34
View File
@@ -186,7 +186,7 @@ export class ServerAPI extends AbstractAPI {
return strings;
}
// @Cache()
@Cache()
async gameStrings(locale: string) {
const group = this.metadata.group("ClientConfigsEntity");
@@ -198,36 +198,28 @@ export class ServerAPI extends AbstractAPI {
const clientConfig = this.metadata.item(group[0].id);
const localizationResources = clientConfig.LocalizationResources();
if (localizationResources.remote_storages.length !== 1) {
throw new RangeError(
`Don't know how to handle ${group.length} remote_storages`,
);
}
const remoteStorageName = localizationResources.remote_storages[0];
let remoteStorage: RemoteStorageComponent | undefined = undefined;
for (const remoteUrl of remoteStorageName.remote_urls) {
const item = this.metadata.item(remoteUrl);
const candidate = item.RemoteStorage();
if (
remoteStorage === undefined ||
candidate.download_speed_weight > remoteStorage.download_speed_weight
) {
remoteStorage = candidate;
}
}
if (remoteStorage === undefined) {
throw new Error("No suitable production remote storage found");
}
const strings: Record<string, string> = {};
const configPath = `${remoteStorage.url}${remoteStorage.relative_path}/config.yaml`;
try {
for (const remoteStorageSettings of localizationResources.remote_storages) {
let remoteStorage: RemoteStorageComponent | undefined = undefined;
for (const remoteUrl of remoteStorageSettings.remote_urls) {
const item = this.metadata.item(remoteUrl);
const candidate = item.RemoteStorage();
if (
remoteStorage === undefined ||
candidate.download_speed_weight > remoteStorage.download_speed_weight
) {
remoteStorage = candidate;
}
}
if (remoteStorage === undefined) {
throw new Error("No suitable production remote storage found");
}
const configPath = `${remoteStorage.url}${remoteStorage.relative_path}/config.yaml`;
const config = await fetch(configPath)
.then((response) => response.text())
.then((text) => parse(text) as LocalizationConfig);
@@ -245,11 +237,6 @@ export class ServerAPI extends AbstractAPI {
Object.assign(strings, namespaceStrings);
}
} catch (error) {
console.warn(
`Failed to fetch config from ${configPath}. Returning empty strings. This will throw an error in production. Error:`,
error,
);
}
return strings;
@@ -5,7 +5,7 @@ import { useLocale } from "./useLocale";
export function useGameStrings(group: string) {
const locale = useLocale();
const strings = useAwait(
() => api._groupedGameStrings(locale, group, true),
() => api.groupedGameStrings(locale, group, true),
`game-strings-${locale}-${group}`,
);
return strings;