Remove role based perms, fetch cosmetics.json from api (#1640)

## Description:

* Fetch cosmetics.json from api
* Remove all role based perms, we are only using flares now
* Created Priviledge refresher which periodically polls /cosmetics.json
endpoint.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I have read and accepted the CLA agreement (only required once).

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
evanpelle
2025-08-04 16:48:41 -07:00
committed by GitHub
parent 3c63e3ffd8
commit 00668dd924
10 changed files with 282 additions and 379 deletions
+35 -30
View File
@@ -1,36 +1,41 @@
import { z } from "zod";
import cosmetics_json from "../../resources/cosmetics/cosmetics.json" with { type: "json" };
import { z } from "zod/v4";
import { RequiredPatternSchema } from "./Schemas";
export const ProductSchema = z.object({
productId: z.string(),
priceId: z.string(),
price: z.string(),
});
const PatternSchema = z.object({
name: z.string(),
pattern: RequiredPatternSchema,
product: ProductSchema.nullable(),
});
// Schema for resources/cosmetics/cosmetics.json
export const CosmeticsSchema = z.object({
role_groups: z.record(z.string(), z.string().array().min(1)),
patterns: z.record(
RequiredPatternSchema,
z.object({
name: z.string(),
role_group: z.string().optional(),
}),
),
flag: z.object({
layers: z.record(
z.string(),
z.object({
name: z.string(),
role_group: z.string().optional(),
flares: z.array(z.string()).optional(),
}),
),
color: z.record(
z.string(),
z.object({
color: z.string(),
name: z.string(),
role_group: z.string().optional(),
flares: z.array(z.string()).optional(),
}),
),
}),
patterns: z.record(z.string(), PatternSchema),
flag: z
.object({
layers: z.record(
z.string(),
z.object({
name: z.string(),
flares: z.array(z.string()).optional(),
}),
),
color: z.record(
z.string(),
z.object({
color: z.string(),
name: z.string(),
flares: z.array(z.string()).optional(),
}),
),
})
.optional(),
});
export type Cosmetics = z.infer<typeof CosmeticsSchema>;
export const COSMETICS: Cosmetics = CosmeticsSchema.parse(cosmetics_json);
export type Pattern = z.infer<typeof PatternSchema>;
export type Product = z.infer<typeof ProductSchema>;
+15 -4
View File
@@ -1,4 +1,4 @@
import { COSMETICS } from "./CosmeticSchemas";
import { Cosmetics } from "./CosmeticSchemas";
const ANIMATION_DURATIONS: Record<string, number> = {
rainbow: 4000,
@@ -11,7 +11,18 @@ const ANIMATION_DURATIONS: Record<string, number> = {
water: 6200,
};
export function renderPlayerFlag(flag: string, target: HTMLElement) {
// TODO: Pass in cosmetics as a parameter when
// remote cosmetics are implemented for custom flags
export function renderPlayerFlag(
flag: string,
target: HTMLElement,
cosmetics: Cosmetics | undefined = undefined,
) {
if (cosmetics === undefined) {
console.warn("No cosmetics provided for flag", flag);
return;
}
if (!flag.startsWith("!")) return;
const code = flag.slice("!".length);
@@ -26,7 +37,7 @@ export function renderPlayerFlag(flag: string, target: HTMLElement) {
target.style.aspectRatio = "3/4";
for (const { layerKey, colorKey } of layers) {
const layerName = COSMETICS.flag.layers[layerKey]?.name ?? layerKey;
const layerName = cosmetics?.flag?.layers[layerKey]?.name ?? layerKey;
const mask = `/flags/custom/${layerName}.svg`;
if (!mask) continue;
@@ -38,7 +49,7 @@ export function renderPlayerFlag(flag: string, target: HTMLElement) {
layer.style.width = "100%";
layer.style.height = "100%";
const colorValue = COSMETICS.flag.color[colorKey]?.color ?? colorKey;
const colorValue = cosmetics?.flag?.color[colorKey]?.color ?? colorKey;
const isSpecial =
!colorValue.startsWith("#") &&
!/^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(colorValue);