Skip to content

Commit

Permalink
🐛 fix(colors): fix colors conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
thrownullexception committed Jul 18, 2024
1 parent e363659 commit c09dca8
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 125 deletions.
8 changes: 7 additions & 1 deletion src/__tests__/_/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const waitForSkeletonsToVanish = async () => {
};

export const getToastMessage = async () => {
return (await within(screen.getByRole("region")).findByRole("status"))
return (await within(await screen.findByRole("region")).findByRole("status"))
.textContent;
};

Expand Down Expand Up @@ -48,3 +48,9 @@ export const selectCombobox = async (label: string, value: string) => {
pointerEventsCheck: PointerEventsCheckLevel.Never,
});
};

export const waitPointerEvents = async () => {
await waitFor(() => {
expect(document.body).toHaveStyle("pointer-events: auto");
});
};
9 changes: 8 additions & 1 deletion src/__tests__/integrations/variables__constants.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
confirmDelete,
getTableRows,
getToastMessage,
waitPointerEvents,
} from "@/tests/utils";

setupApiHandlers();
Expand Down Expand Up @@ -63,13 +64,17 @@ describe("pages/integrations/variables => constants", () => {

const dialog = await screen.findByRole("dialog");

await waitPointerEvents();

expect(within(dialog).getByText("Create Constant")).toBeInTheDocument();

await userEvent.type(within(dialog).getByLabelText("Key"), "NEW_KEY");
await userEvent.type(within(dialog).getByLabelText("Value"), "new value");

await userEvent.click(
within(dialog).getByRole("button", { name: "Create Constant" })
within(dialog).getByRole("button", {
name: "Create Constant",
})
);

expect(await getToastMessage()).toBe("Constant Saved Successfully");
Expand Down Expand Up @@ -119,6 +124,8 @@ describe("pages/integrations/variables => constants", () => {
})
);

await waitPointerEvents();

const dialog = screen.getByRole("dialog");

expect(within(dialog).getByText("Update Constant")).toBeInTheDocument();
Expand Down
8 changes: 1 addition & 7 deletions src/__tests__/integrations/variables__credentials.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,7 @@ describe("pages/integrations/variables => credentials", () => {
)
);

const dialog = await screen.findByRole(
"dialog",
{},
{
timeout: 20000,
}
);
const dialog = await screen.findByRole("dialog");

expect(within(dialog).getByText("Create Secret")).toBeInTheDocument();

Expand Down
69 changes: 4 additions & 65 deletions src/frontend/_layouts/useAppTheme.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,17 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-bitwise */
import { useAppConfiguration } from "@/frontend/hooks/configuration/configuration.store";

import { hexToOklch } from "../lib/colors/conversion";
import { usePortalThemes } from "./portal";

type ThreeNumbers = readonly [number, number, number];

function hexToRgb(hex$1: string): ThreeNumbers {
let hex = hex$1.replace("#", "");
if (hex.length === 3) {
hex = hex
.split("")
.map((c) => c + c)
.join("");
}
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return [r, g, b];
}

function rgbToXyz([r, g, b]: ThreeNumbers) {
r /= 255;
g /= 255;
b /= 255;

r = r > 0.04045 ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92;
g = g > 0.04045 ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92;
b = b > 0.04045 ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92;

const x = (r * 0.4124564 + g * 0.3575761 + b * 0.1804375) / 0.95047;
const y = (r * 0.2126729 + g * 0.7151522 + b * 0.072175) / 1.0;
const z = (r * 0.0193339 + g * 0.119192 + b * 0.9503041) / 1.08883;

return [x, y, z] as const;
}

function xyzToLab([x, y, z]: ThreeNumbers) {
x = x > 0.008856 ? Math.cbrt(x) : 7.787 * x + 16 / 116;
y = y > 0.008856 ? Math.cbrt(y) : 7.787 * y + 16 / 116;
z = z > 0.008856 ? Math.cbrt(z) : 7.787 * z + 16 / 116;

const l = 116 * y - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);

return [l, a, b] as const;
}

function labToOklch([l, a, b]: ThreeNumbers) {
const c = Math.sqrt(a * a + b * b);
const h = Math.atan2(b, a);
const hDegrees = (h * 180) / Math.PI;
const hPositive = hDegrees < 0 ? hDegrees + 360 : hDegrees;

return [l / 100, c / 100, hPositive] as const;
}

function hexToOklch(hex: string) {
const rgb = hexToRgb(hex);
const xyz = rgbToXyz(rgb);
const lab = xyzToLab(xyz);
const [l, c, h] = labToOklch(lab);
return `${l.toFixed(3)}% ${c.toFixed(3)} ${h.toFixed(3)}`;
}

export const useAppTheme = () => {
const themeColor = useAppConfiguration("theme_color");

usePortalThemes();

const { l, c, h } = hexToOklch(themeColor.data.primary);

document.documentElement.style.setProperty(
"--app-primary",
hexToOklch(themeColor.data.primary)
`${l}% ${c} ${h}`
);
};
28 changes: 28 additions & 0 deletions src/frontend/components/app/app-blur.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ReactNode } from "react";
import { useEffect } from "react";

interface IProps {
children: ReactNode;
isOn: boolean;
}

const gaussianPortals = ["gaussian-portal-0", "gaussian-portal-1"];

export function AppBlur({ children, isOn }: IProps): JSX.Element {
useEffect(() => {
if (isOn) {
gaussianPortals.forEach((portal) => {
document.getElementById(portal)?.classList.add("gaussian-blur");
});
setTimeout(() => {
document.body.style.pointerEvents = "auto";
}, 500);
} else {
gaussianPortals.forEach((portal) => {
document.getElementById(portal)?.classList.remove("gaussian-blur");
});
}
}, [isOn]);

return children as JSX.Element;
}
12 changes: 4 additions & 8 deletions src/frontend/components/app/confirm-alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from "@/components/ui/alert-dialog";
import { createStore } from "@/frontend/lib/store";

import { NextPortal } from "./next-portal";
import { AppBlur } from "./app-blur";

interface IConfirmAlertDetails {
title: MessageDescriptor;
Expand Down Expand Up @@ -51,16 +51,12 @@ export function ConfirmAlert() {
store.onClose,
]);

if (!title) {
return null;
}

return (
<NextPortal>
<AppBlur isOn={!!title}>
<AlertDialog open={!!title} onOpenChange={onClose}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> {_(title)}</AlertDialogTitle>
<AlertDialogTitle> {title ? _(title) : null}</AlertDialogTitle>
<AlertDialogDescription>
{t`Are you sure you want to do this?`}
</AlertDialogDescription>
Expand All @@ -76,6 +72,6 @@ export function ConfirmAlert() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</NextPortal>
</AppBlur>
);
}
36 changes: 0 additions & 36 deletions src/frontend/components/app/next-portal.tsx

This file was deleted.

10 changes: 3 additions & 7 deletions src/frontend/components/app/off-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { cn } from "@/components/utils";
import { ScrollArea } from "../ui/scroll-area";
import { Separator } from "../ui/separator";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../ui/sheet";
import { NextPortal } from "./next-portal";
import { AppBlur } from "./app-blur";

export interface IProps {
show: boolean;
Expand All @@ -20,12 +20,8 @@ export interface IProps {
export function OffCanvas({ show, onClose, title, children, size }: IProps) {
const { _ } = useLingui();

if (!show) {
return null;
}

return (
<NextPortal>
<AppBlur isOn={show}>
<Sheet open={show} onOpenChange={onClose}>
<SheetContent
className={cn("flex w-full flex-col", {
Expand All @@ -43,6 +39,6 @@ export function OffCanvas({ show, onClose, title, children, size }: IProps) {
</ScrollArea>
</SheetContent>
</Sheet>
</NextPortal>
</AppBlur>
);
}
61 changes: 61 additions & 0 deletions src/frontend/lib/colors/conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
type TRGB = { r: number; g: number; b: number };
type TLCH = { l: number; c: number; h: number };

export const hexToOklch = (hex: string) => {
const hexToRGB = (h: string): TRGB => {
const r: number = parseInt(h.slice(1, 3), 16);
const g: number = parseInt(h.slice(3, 5), 16);
const b: number = parseInt(h.slice(5, 7), 16);
return { r, g, b };
};

const rgbToOklch = (rgb: TRGB): TLCH => {
const r = rgb.r / 255;
const g = rgb.g / 255;
const b = rgb.b / 255;

const [linearR, linearG, linearB] = [r, g, b].map((c: number) =>
c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
);

// Convert Linear RGB to CIE XYZ
let x: number =
linearR * 0.4124564 + linearG * 0.3575761 + linearB * 0.1804375;
let y: number =
linearR * 0.2126729 + linearG * 0.7151522 + linearB * 0.072175;
let z: number =
linearR * 0.0193339 + linearG * 0.119192 + linearB * 0.9503041;

// Convert CIE XYZ to CIELAB
[x, y, z] = [x, y, z].map((c: number) =>
c > 0.008856 ? c ** (1 / 3) : (903.3 * c + 16) / 116
);
let l: number = 116 * y - 16;
const a: number = 500 * (x - y);
const bStar: number = 200 * (y - z);

// Convert CIELAB to Oklch
// let c: number = Math.sqrt(a * a + bStar * bStar);
let h: number = Math.atan2(bStar, a) * (180 / Math.PI);
if (h < 0) h += 360;

// Assume c_max is the maximum chroma value observed or expected in your conversions
const c_max = 100; /* your determined or observed maximum chroma value */
// Adjusted part of the rgbToOklch function for calculating 'c'
let c: number = Math.sqrt(a * a + bStar * bStar);
c = (c / c_max) * 0.37; // Scale c to be within 0 and 0.37

// Scale and round values to match the specified ranges
l = Math.round(((l + 16) / 116) * 1000) / 10; // Scale l to be between 0 and 1
c = Number(c.toFixed(2)); // Ensure c is correctly scaled, adjust if necessary based on your color space calculations
h = Number(h.toFixed(1)); // h is already within 0 to 360

return {
l,
c,
h,
};
};

return rgbToOklch(hexToRGB(hex));
};

0 comments on commit c09dca8

Please sign in to comment.