Spaces:
Runtime error
Runtime error
| <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> | |