ai / frontend /src /lib /components /chat /Settings /Account /UserProfileImage.svelte
fdrah
initial-commit
9e93b10
Raw
History Blame Contribute Delete
4.2 kB
<script lang="ts">
import { toast } from "svelte-sonner";
import { getContext } from "svelte";
const i18n = getContext("i18n");
import { getGravatarUrl } from "$lib/apis/utils";
import { canvasPixelTest, generateInitialsImage } from "$lib/utils";
import { REXPRO_BASE_URL } from "$lib/constants";
export let profileImageUrl;
export let user = null;
export let imageClassName = "size-14 md:size-18";
let profileImageInputElement;
</script>
<input
id="profile-image-input"
bind:this={profileImageInputElement}
type="file"
hidden
accept="image/*"
on:change={(e) => {
const files = profileImageInputElement.files ?? [];
let reader = new FileReader();
reader.onload = (event) => {
let originalImageUrl = `${event.target.result}`;
const img = new Image();
img.src = originalImageUrl;
img.onload = function () {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// Calculate the aspect ratio of the image
const aspectRatio = img.width / img.height;
// Calculate the new width and height to fit within 250x250
let newWidth, newHeight;
if (aspectRatio > 1) {
newWidth = 250 * aspectRatio;
newHeight = 250;
} else {
newWidth = 250;
newHeight = 250 / aspectRatio;
}
// Set the canvas size
canvas.width = 250;
canvas.height = 250;
// Calculate the position to center the image
const offsetX = (250 - newWidth) / 2;
const offsetY = (250 - newHeight) / 2;
// Draw the image on the canvas
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
// Get the base64 representation of the compressed image
const compressedSrc = canvas.toDataURL("image/webp", 0.8);
// Display the compressed image
profileImageUrl = compressedSrc;
profileImageInputElement.files = null;
};
};
if (
files.length > 0 &&
["image/gif", "image/webp", "image/jpeg", "image/png"].includes(
files[0]["type"],
)
) {
reader.readAsDataURL(files[0]);
}
}}
/>
<div class="flex flex-col self-start group">
<div class="self-center flex">
<button
class="relative rounded-full dark:bg-gray-700"
type="button"
on:click={() => {
profileImageInputElement.click();
}}
>
<img
src={profileImageUrl !== ""
? profileImageUrl
: generateInitialsImage(user?.name)}
alt="profile"
class=" rounded-full {imageClassName} object-cover"
/>
<div
class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition"
>
<div
class="p-1 rounded-full bg-white text-black border-gray-100 shadow"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-3"
>
<path
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
/>
</svg>
</div>
</div>
</button>
</div>
<div class="flex flex-col w-full justify-center mt-2">
<button
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
type="button"
on:click={async () => {
profileImageUrl = `${REXPRO_BASE_URL}/user.png`;
}}>{$i18n.t("Remove")}</button
>
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
type="button"
on:click={async () => {
if (canvasPixelTest()) {
profileImageUrl = generateInitialsImage(user?.name);
} else {
toast.info(
$i18n.t(
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.",
),
{
duration: 1000 * 10,
},
);
}
}}>{$i18n.t("Initials")}</button
>
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
type="button"
on:click={async () => {
const url = await getGravatarUrl(
localStorage.token,
user?.email,
);
profileImageUrl = url;
}}>{$i18n.t("Gravatar")}</button
>
</div>
</div>