feat: ship minecraft theme updates and platform workflow improvements
这个提交包含在:
@@ -3,9 +3,9 @@ export const API_BASE =
|
||||
(process.env.NODE_ENV === "development" ? "http://localhost:8080" : "/admin139");
|
||||
|
||||
function uiText(zhText: string, enText: string): string {
|
||||
if (typeof window === "undefined") return enText;
|
||||
if (typeof window === "undefined") return zhText;
|
||||
const lang = window.localStorage.getItem("csp.ui.language");
|
||||
return lang === "zh" ? zhText : enText;
|
||||
return lang === "en" ? enText : zhText;
|
||||
}
|
||||
|
||||
type ApiEnvelope<T> =
|
||||
|
||||
@@ -186,21 +186,5 @@ export function analyzeCpp14Policy(code: string): Cpp14PolicyIssue[] {
|
||||
);
|
||||
}
|
||||
|
||||
if (!/\bfreopen\s*\(/.test(text) && /\bint\s+main\s*\(/.test(text)) {
|
||||
const idx = text.search(/\bint\s+main\s*\(/);
|
||||
const pos = offsetToPosition(lineStarts, Math.max(0, idx));
|
||||
pushIssue(
|
||||
issues,
|
||||
"freopen-tip",
|
||||
"hint",
|
||||
"未检测到 freopen(福建二轮常见文件读写要求)",
|
||||
"若考场题面要求 *.in/*.out,请按官方文件名补上 freopen。",
|
||||
pos.line,
|
||||
pos.column,
|
||||
pos.line,
|
||||
pos.column + 3
|
||||
);
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||
|
||||
export function readUiLanguage(): "en" | "zh" {
|
||||
if (typeof window === "undefined") return "en";
|
||||
return window.localStorage.getItem("csp.ui.language") === "zh" ? "zh" : "en";
|
||||
if (typeof window === "undefined") return "zh";
|
||||
return window.localStorage.getItem("csp.ui.language") === "en" ? "en" : "zh";
|
||||
}
|
||||
|
||||
export function useI18nText() {
|
||||
const { language } = useUiPreferences();
|
||||
const isZh = language === "zh";
|
||||
const tx = (zhText: string, enText: string) => (isZh ? zhText : enText);
|
||||
const tx = useCallback(
|
||||
(zhText: string, enText: string) => (isZh ? zhText : enText),
|
||||
[isZh]
|
||||
);
|
||||
return { language, isZh, tx };
|
||||
}
|
||||
|
||||
74
frontend/src/lib/pixel-avatar.ts
普通文件
74
frontend/src/lib/pixel-avatar.ts
普通文件
@@ -0,0 +1,74 @@
|
||||
function hashSeed(seed: string): number {
|
||||
let h = 2166136261 >>> 0;
|
||||
for (let i = 0; i < seed.length; i += 1) {
|
||||
h ^= seed.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619) >>> 0;
|
||||
}
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
function mulberry32(seed: number): () => number {
|
||||
let t = seed >>> 0;
|
||||
return () => {
|
||||
t += 0x6d2b79f5;
|
||||
let x = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
|
||||
return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
const SKIN_PALETTE = ["#f6d3b3", "#e6be95", "#d7a980", "#f8dec6", "#c99264"];
|
||||
const HAIR_PALETTE = ["#2f1b12", "#4a2f1d", "#6d4c41", "#212121", "#4e342e", "#1b5e20"];
|
||||
const ACCENT_PALETTE = ["#7cb342", "#00b0d6", "#ffb300", "#8d6e63", "#ab47bc", "#ef5350"];
|
||||
|
||||
export function buildAvatarSeed(...parts: Array<string | number | null | undefined>): string {
|
||||
const cleaned = parts
|
||||
.map((part) => String(part ?? "").trim())
|
||||
.filter((part) => part.length > 0);
|
||||
return cleaned.length > 0 ? cleaned.join("|") : "guest|pixel";
|
||||
}
|
||||
|
||||
export function generatePixelAvatarDataUri(seedInput: string, pixelSize = 10): string {
|
||||
const seed = buildAvatarSeed(seedInput);
|
||||
const rng = mulberry32(hashSeed(seed));
|
||||
const skin = SKIN_PALETTE[Math.floor(rng() * SKIN_PALETTE.length)] ?? SKIN_PALETTE[0];
|
||||
const hair = HAIR_PALETTE[Math.floor(rng() * HAIR_PALETTE.length)] ?? HAIR_PALETTE[0];
|
||||
const accent = ACCENT_PALETTE[Math.floor(rng() * ACCENT_PALETTE.length)] ?? ACCENT_PALETTE[0];
|
||||
const bg = rng() > 0.5 ? "#1f1f1f" : "#2b2b2b";
|
||||
const border = rng() > 0.5 ? "#000000" : "#3e2723";
|
||||
|
||||
const width = 8 * pixelSize;
|
||||
const height = 8 * pixelSize;
|
||||
const rects: string[] = [
|
||||
`<rect width="${width}" height="${height}" fill="${bg}"/>`,
|
||||
`<rect x="0" y="0" width="${width}" height="${pixelSize}" fill="${accent}" opacity="0.4"/>`,
|
||||
];
|
||||
|
||||
for (let y = 0; y < 8; y += 1) {
|
||||
for (let x = 0; x < 4; x += 1) {
|
||||
const fillFace = y >= 1 && y <= 6 && x >= 1;
|
||||
const threshold = fillFace ? 0.2 : 0.52;
|
||||
if (rng() < threshold) continue;
|
||||
|
||||
const color = y <= 1 ? hair : y >= 6 ? accent : skin;
|
||||
const leftX = x * pixelSize;
|
||||
const rightX = (7 - x) * pixelSize;
|
||||
const yPos = y * pixelSize;
|
||||
rects.push(`<rect x="${leftX}" y="${yPos}" width="${pixelSize}" height="${pixelSize}" fill="${color}"/>`);
|
||||
if (rightX !== leftX) {
|
||||
rects.push(`<rect x="${rightX}" y="${yPos}" width="${pixelSize}" height="${pixelSize}" fill="${color}"/>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eyeY = 3 * pixelSize;
|
||||
rects.push(`<rect x="${2 * pixelSize}" y="${eyeY}" width="${pixelSize}" height="${pixelSize}" fill="#111"/>`);
|
||||
rects.push(`<rect x="${5 * pixelSize}" y="${eyeY}" width="${pixelSize}" height="${pixelSize}" fill="#111"/>`);
|
||||
|
||||
const mouthY = 5 * pixelSize;
|
||||
rects.push(`<rect x="${3 * pixelSize}" y="${mouthY}" width="${2 * pixelSize}" height="${pixelSize}" fill="${hair}" opacity="0.8"/>`);
|
||||
rects.push(`<rect x="${0}" y="${0}" width="${width}" height="${height}" fill="none" stroke="${border}" stroke-width="${Math.max(2, Math.floor(pixelSize / 4))}"/>`);
|
||||
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" shape-rendering="crispEdges">${rects.join("")}</svg>`;
|
||||
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户