Skip to content

Commit

Permalink
fix: leaderboard height adjustment issue
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Jan 5, 2024
1 parent 4db1dde commit 31fef0b
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 84 deletions.
2 changes: 1 addition & 1 deletion packages/canvacord/src/assets/Font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ export class Font {
*/
public static loadDefault() {
return this.fromBuffer(Fonts.Geist, "geist");
return Font.fromBuffer(Fonts.Geist, "geist");
}
}
7 changes: 6 additions & 1 deletion packages/canvacord/src/assets/TemplateFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class TemplateImage {
*/
public async resolve(): Promise<Image> {
if (this.#resolved) return this.#resolved;
// biome-ignore lint: assignment should not be an expression
return (this.#resolved = await createCanvasImage(this.source));
}
}
Expand All @@ -36,7 +37,11 @@ export class TemplateImage {
* Creates a new template from the provided template.
* @param template The template to create from
*/
export const createTemplate = <F extends (...args: any[]) => any, P extends Parameters<F>>(
export const createTemplate = <
// biome-ignore lint: any is tolerable here
F extends (...args: any[]) => any,
P extends Parameters<F>,
>(
cb: (...args: P) => IImageGenerationTemplate,
) => {
return (...args: Parameters<typeof cb>) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/canvacord/src/canvas/Canvacord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ for (const key in TemplateFactory) {
const method = key.toLowerCase() as Lowercase<keyof typeof TemplateFactory>;
if (method === "triggered") continue;

factory[method] = async function (...args: Parameters<CanvacordFactory[typeof method]>) {
factory[method] = async (...args: Parameters<CanvacordFactory[typeof method]>) => {
// @ts-expect-error
const template = TemplateFactory[capitalize(method)](...args);
const generator = new ImageGen(template);
Expand Down Expand Up @@ -100,6 +100,7 @@ export interface CanvacordInit {
* @returns The image processor
*/
function CanvacordConstructor(source: ImageSource, options?: CanvacordInit) {
// biome-ignore lint: reassignment is tolerable here
options ??= {};

const img = new CanvasImage(source, options?.width ?? -1, options?.height ?? -1);
Expand Down
4 changes: 4 additions & 0 deletions packages/canvacord/src/canvas/CanvasImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export class CanvasImage extends ImageFilterer {
this.steps.push(async (ctx) => {
const img = this.#img || this.#setImg(await createCanvasImage(this.source));

// biome-ignore lint: reassignment
width ??= this.width;
// biome-ignore lint: reassignment
height ??= this.height;

ctx.drawImage(img, x, y, width, height);
Expand All @@ -51,7 +53,9 @@ export class CanvasImage extends ImageFilterer {
*/
public circle(width?: number, height?: number) {
this.steps.push((ctx) => {
// biome-ignore lint: reassignment
width ??= ctx.canvas.width;
// biome-ignore lint: reassignment
height ??= ctx.canvas.height;

ctx.globalCompositeOperation = "destination-in";
Expand Down
1 change: 1 addition & 0 deletions packages/canvacord/src/canvas/ImageFilterer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class ImageFilterer extends CanvasHelper {
if (this.#filters.length) ctx.filter = this.#filters.join(" ");

while (this.steps.length > 0) {
// biome-ignore lint: non-null assertion
await this.steps.shift()!(ctx);
}
}
Expand Down
10 changes: 9 additions & 1 deletion packages/canvacord/src/canvas/ImageGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,13 @@ export class ImageGen extends Encodable {
}

async #inferSize() {
if (this.template.isInferrable()) return { width: this.template.getWidth()!, height: this.template.getHeight()! };
if (this.template.isInferrable())
return {
// biome-ignore lint: non-null assertion
width: this.template.getWidth()!,
// biome-ignore lint: non-null assertion
height: this.template.getHeight()!,
};

if (!this.template.steps.length) throw new Error("Cannot infer size from empty template");
const firstImg = this.template.steps.find((s) => s.image?.length)?.image?.[0];
Expand Down Expand Up @@ -344,6 +350,7 @@ export class ImageGen extends Encodable {
if (options.framerate != null) encoder.setFramerate(options.framerate);
if (options.transparent != null) encoder.setTransparent(options.transparent);

// biome-ignore lint: assignment should not be an expression
const canvas = (this._canvas = createCanvas(width, height));
const ctx = canvas.getContext("2d");

Expand All @@ -368,6 +375,7 @@ export class ImageGen extends Encodable {
public async render() {
const { width, height } = await this.#inferSize();

// biome-ignore lint: assignment should not be an expression
const canvas = (this._canvas = createCanvas(width, height));
const ctx = canvas.getContext("2d");

Expand Down
1 change: 0 additions & 1 deletion packages/canvacord/src/canvas/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { loadImage as createImage, SKRSContext2D } from "@napi-rs/canvas";
* @returns The canvas image
*
* const image = await createCanvasImage('https://example.com/image.png');
*/
export const createCanvasImage = async (img: ImageSource) => {
const canvacordImg = await loadImage(img);
Expand Down
113 changes: 88 additions & 25 deletions packages/canvacord/src/components/LeaderboardBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,14 @@ export interface LeaderboardProps {

const Crown = () => {
return (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
// biome-ignore lint: alternative text title
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.5 17.5H3.5C3.225 17.5 3 17.7813 3 18.125V19.375C3 19.7188 3.225 20 3.5 20H16.5C16.775 20 17 19.7188 17 19.375V18.125C17 17.7813 16.775 17.5 16.5 17.5ZM18.5 5C17.6719 5 17 5.83984 17 6.875C17 7.15234 17.05 7.41016 17.1375 7.64844L14.875 9.34375C14.3937 9.70313 13.7719 9.5 13.4937 8.89063L10.9469 3.32031C11.2812 2.97656 11.5 2.46094 11.5 1.875C11.5 0.839844 10.8281 0 10 0C9.17188 0 8.5 0.839844 8.5 1.875C8.5 2.46094 8.71875 2.97656 9.05313 3.32031L6.50625 8.89063C6.22812 9.5 5.60312 9.70313 5.125 9.34375L2.86562 7.64844C2.95 7.41406 3.00312 7.15234 3.00312 6.875C3.00312 5.83984 2.33125 5 1.50312 5C0.675 5 0 5.83984 0 6.875C0 7.91016 0.671875 8.75 1.5 8.75C1.58125 8.75 1.6625 8.73438 1.74063 8.71875L4 16.25H16L18.2594 8.71875C18.3375 8.73438 18.4188 8.75 18.5 8.75C19.3281 8.75 20 7.91016 20 6.875C20 5.83984 19.3281 5 18.5 5Z"
fill="#FFAA00"
Expand All @@ -90,6 +97,7 @@ const Crown = () => {
};

const MIN_RENDER_HEIGHT = 420;
const HEIGHT_INTERVAL = [394, 498, 594, 690, 786, 882, 978, 1074] as const;

export class LeaderboardBuilder extends Builder<LeaderboardProps> {
/**
Expand Down Expand Up @@ -173,14 +181,13 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
throw new RangeError("Number of players must be greater than 0");
}

const minh = options.header ? MIN_RENDER_HEIGHT - 130 : MIN_RENDER_HEIGHT;
const calculatedHeight = minh + (total - 3) * 90;
const diff = total >= 7 ? total - 7 : 0;
const incremented = 10 * diff;
this.height = Math.max(calculatedHeight + incremented, minh);
const adjustedHeight = HEIGHT_INTERVAL[total - 3] ?? MIN_RENDER_HEIGHT;

this.height = adjustedHeight;

this.adjustCanvas();

// biome-ignore lint: declare variables separately
let background, headerImg;

if (options.background) {
Expand All @@ -191,29 +198,51 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
headerImg = await loadImage(options.header.image);
}

const winners = [options.players[1], options.players[0], options.players[2]].filter(Boolean);
const winners = [
options.players[1],
options.players[0],
options.players[2],
].filter(Boolean);

return (
<div className="h-full w-full flex relative">
{background && <img src={background.toDataURL()} className="absolute top-0 left-0 h-full w-full" />}
{background && (
<img
src={background.toDataURL()}
className="absolute top-0 left-0 h-full w-full"
alt="background"
/>
)}
<div className="py-[30px] flex flex-col items-center w-full">
{options.header && headerImg ? (
<div className="flex items-center justify-center flex-col w-full">
<img src={headerImg.toDataURL()} className="rounded-full w-16 h-w-16" />
<h1 className="text-white text-xl font-extrabold m-0 mt-2">{options.header.title}</h1>
<h2 className="text-white text-sm font-thin m-0">{options.header.subtitle}</h2>
<img
src={headerImg.toDataURL()}
className="rounded-full w-16 h-w-16"
alt="header"
/>
<h1 className="text-white text-xl font-extrabold m-0 mt-2">
{options.header.title}
</h1>
<h2 className="text-white text-sm font-thin m-0">
{options.header.subtitle}
</h2>
</div>
) : null}
<div
className={StyleSheet.cn(
"flex flex-row w-[90%] justify-center items-center mt-16",
winners.length ? "mt-24" : "",
winners.length ? "mt-24" : ""
)}
>
{await Promise.all(winners.map((winner) => this.renderTop(winner)))}
</div>
{this.renderPlayers(
await Promise.all(options.players.filter((f) => !winners.includes(f)).map((m) => this.renderPlayer(m))),
await Promise.all(
options.players
.filter((f) => !winners.includes(f))
.map((m) => this.renderPlayer(m))
)
)}
</div>
</div>
Expand All @@ -224,23 +253,35 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
* Render players ui on the canvas
*/
public renderPlayers(players: JSX.Element[]) {
return <div className="mt-4 flex flex-col items-center justify-center w-[95%]">{players}</div>;
return (
<div className="mt-4 flex flex-col items-center justify-center w-[95%]">
{players}
</div>
);
}

/**
* Render top players ui on the canvas
*/
public async renderTop({ avatar, displayName, level, rank, username, xp }: LeaderboardProps["players"][number]) {
public async renderTop({
avatar,
displayName,
level,
rank,
username,
xp,
}: LeaderboardProps["players"][number]) {
const image = await loadImage(avatar);
const currentColor = DefaultColors[rank === 1 ? "Yellow" : rank === 2 ? "Blue" : "Green"];
const currentColor =
DefaultColors[rank === 1 ? "Yellow" : rank === 2 ? "Blue" : "Green"];
const crown = rank === 1;

return (
<div
className={StyleSheet.cn(
"relative flex flex-col items-center justify-center p-4 bg-[#1E2237CC] w-[35%] rounded-md",
crown ? "-mt-4 bg-[#252A40CC] rounded-b-none h-[113%]" : "",
rank === 2 ? "rounded-br-none" : rank === 3 ? "rounded-bl-none" : "",
rank === 2 ? "rounded-br-none" : rank === 3 ? "rounded-bl-none" : ""
)}
>
{crown && (
Expand All @@ -251,7 +292,10 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
<div className="flex items-center justify-center flex-col absolute -top-10">
<img
src={image.toDataURL()}
className={StyleSheet.cn(`border-[3px] border-[${currentColor}] rounded-full h-18 w-18`)}
className={StyleSheet.cn(
`border-[3px] border-[${currentColor}] rounded-full h-18 w-18`
)}
alt="avatar"
/>
<div
className={`flex items-center justify-center text-xs p-2 text-center font-bold h-3 w-3 rounded-full text-white absolute bg-[${currentColor}] -bottom-[0.4rem]`}
Expand All @@ -260,13 +304,16 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
</div>
</div>
<div className="flex flex-col items-center justify-center mt-5">
<h1 className="text-white text-base font-extrabold m-0">{displayName}</h1>
<h1 className="text-white text-base font-extrabold m-0">
{displayName}
</h1>
<h2 className="text-white text-xs font-thin m-0 mb-2">@{username}</h2>
<h4 className={`text-sm text-[${currentColor}] m-0`}>
{this.options.get("text").level} {level}
</h4>
<h4 className={`text-sm text-[${currentColor}] m-0`}>
{fixed(xp, this.options.get("abbreviate"))} {this.options.get("text").xp}
{fixed(xp, this.options.get("abbreviate"))}{" "}
{this.options.get("text").xp}
</h4>
</div>
</div>
Expand All @@ -276,19 +323,34 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
/**
* Render player ui on the canvas
*/
public async renderPlayer({ avatar, displayName, level, rank, username, xp }: LeaderboardProps["players"][number]) {
public async renderPlayer({
avatar,
displayName,
level,
rank,
username,
xp,
}: LeaderboardProps["players"][number]) {
const image = await loadImage(avatar);

return (
<div className="bg-[#252A40BB] p-4 rounded-md flex flex-row justify-between items-center w-full mb-2">
<div className="flex flex-row">
<div className="flex flex-col items-center justify-center mr-2">
<h1 className="text-white font-extrabold text-xl m-0">{rank}</h1>
<h4 className="text-white font-medium text-sm m-0">{this.options.get("text").rank}</h4>
<h4 className="text-white font-medium text-sm m-0">
{this.options.get("text").rank}
</h4>
</div>
<img src={image.toDataURL()} className="rounded-full h-14 w-14 mr-2" />
<img
src={image.toDataURL()}
className="rounded-full h-14 w-14 mr-2"
alt="avatar"
/>
<div className="flex flex-col items-start justify-center">
<h1 className="text-white font-extrabold text-xl m-0">{displayName}</h1>
<h1 className="text-white font-extrabold text-xl m-0">
{displayName}
</h1>
<h4 className="text-white font-medium text-sm m-0">@{username}</h4>
</div>
</div>
Expand All @@ -297,7 +359,8 @@ export class LeaderboardBuilder extends Builder<LeaderboardProps> {
{this.options.get("text").level} {level}
</h4>
<h4 className="text-white font-medium text-sm m-0">
{fixed(xp, this.options.get("abbreviate"))} {this.options.get("text").xp}
{fixed(xp, this.options.get("abbreviate"))}{" "}
{this.options.get("text").xp}
</h4>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/canvacord/src/components/RankCardBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export class RankCardBuilder extends Builder<RankCardBuilderProps> {
{...{
...options,
avatar: avatar.toDataURL(),
// biome-ignore lint: forbidden non-null assertion
backgroundColor: background!,
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function NeoClassicalCard(props: RankCardProps) {

const shouldSkipStats = currentXP == null && requiredXP == null;
const progress = calculateProgress(currentXP ?? 0, requiredXP ?? 0);
const progressWidth = typeof progress !== "number" || isNaN(progress) ? 0 : clamp(progress);
const progressWidth = typeof progress !== "number" || Number.isNaN(progress) ? 0 : clamp(progress);

return (
<div
Expand All @@ -111,8 +111,8 @@ export function NeoClassicalCard(props: RankCardProps) {
)}
style={StyleSheet.compose(
{
backgroundImage: backgroundColor && backgroundColor.startsWith("url(") ? backgroundColor : undefined,
backgroundSize: backgroundColor && backgroundColor.startsWith("url(") ? "100% 100%" : undefined,
backgroundImage: backgroundColor?.startsWith("url(") ? backgroundColor : undefined,
backgroundSize: backgroundColor?.startsWith("url(") ? "100% 100%" : undefined,
},
StyleSheet.css(styles.background),
)}
Expand All @@ -134,6 +134,7 @@ export function NeoClassicalCard(props: RankCardProps) {
style={StyleSheet.css(styles.avatar?.container)}
>
<img
alt="avatar"
src={avatar}
className={StyleSheet.cn("h-38 w-38 rounded-full ml-4", StyleSheet.tw(styles.avatar?.image))}
style={StyleSheet.css(styles.avatar?.image)}
Expand Down
6 changes: 5 additions & 1 deletion packages/canvacord/src/helpers/StyleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export type CSSPropertiesLike<K extends string | number | symbol = string> = Rec
/**
* Performs object cleanup by deleting all undefined properties that could interfere with builder methods.
*/
export const performObjectCleanup = (obj: Record<string, any>, deep = false) => {
export const performObjectCleanup = (
// biome-ignore lint: any is tolerated here
obj: Record<string, any>,
deep = false,
) => {
for (const prop in obj) {
if (obj[prop] === undefined) delete obj[prop];
if (typeof obj[prop] === "object" && deep) performObjectCleanup(obj[prop], deep);
Expand Down
Loading

1 comment on commit 31fef0b

@3vil3vo
Copy link

@3vil3vo 3vil3vo commented on 31fef0b Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaay

Please sign in to comment.