From 16112d4f1d8d2d97b135966ad0ae1267906fff0a Mon Sep 17 00:00:00 2001 From: Raymond <101374892+raymonable@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:56:44 -0500 Subject: [PATCH] feat: :heavy_plus_sign: pfp cropper + enhancements --- AquaNet/package.json | 1 + AquaNet/src/App.svelte | 2 +- .../settings/GeneralGameSettings.svelte | 4 +- AquaNet/src/libs/i18n/en_ref.ts | 4 +- AquaNet/src/pages/User/Settings.svelte | 85 +++++++++++++++++-- 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/AquaNet/package.json b/AquaNet/package.json index a50f876e..55f89bb1 100644 --- a/AquaNet/package.json +++ b/AquaNet/package.json @@ -39,6 +39,7 @@ "lxgw-wenkai-lite-webfont": "^1.7.0", "modern-normalize": "^3.0.1", "moment": "^2.30.1", + "svelte-easy-crop": "^4.0.0", "svelte5-router": "^3.0.1" }, "packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf" diff --git a/AquaNet/src/App.svelte b/AquaNet/src/App.svelte index 6a320657..760f0ba8 100644 --- a/AquaNet/src/App.svelte +++ b/AquaNet/src/App.svelte @@ -75,7 +75,7 @@ img width: 1.5rem height: 1.5rem - border-radius: 50% + border-radius: vars.$border-radius object-fit: cover .pfp diff --git a/AquaNet/src/components/settings/GeneralGameSettings.svelte b/AquaNet/src/components/settings/GeneralGameSettings.svelte index 65c0a033..6fbbb64a 100644 --- a/AquaNet/src/components/settings/GeneralGameSettings.svelte +++ b/AquaNet/src/components/settings/GeneralGameSettings.svelte @@ -2,7 +2,7 @@ import { fade } from "svelte/transition"; import { FADE_IN, FADE_OUT } from "../../libs/config"; import GameSettingFields from "./GameSettingFields.svelte"; - import { ts } from "../../libs/i18n"; + import { t, ts } from "../../libs/i18n"; import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte"; const rounding = useLocalStorage("rounding", true); @@ -10,7 +10,7 @@

- These settings affect Mai and Wacca. + {ts("settings.gameNotice")}

diff --git a/AquaNet/src/libs/i18n/en_ref.ts b/AquaNet/src/libs/i18n/en_ref.ts index 80dc168a..658d0b7c 100644 --- a/AquaNet/src/libs/i18n/en_ref.ts +++ b/AquaNet/src/libs/i18n/en_ref.ts @@ -158,6 +158,7 @@ export const EN_REF_SETTINGS = { 'settings.mai2.name': 'Player Name', 'settings.profile.picture': 'Profile Picture', 'settings.profile.upload-new': 'Upload New', + 'settings.profile.bad-format': 'Invalid image format. Supported types are PNG, JPG, JPEG, WEBP & GIF.', 'settings.profile.save': 'Save', 'settings.profile.name': 'Display Name', 'settings.profile.username': 'Username', @@ -167,7 +168,8 @@ export const EN_REF_SETTINGS = { 'settings.profile.unset': 'Unset', 'settings.profile.unchanged': 'Unchanged', 'settings.export': 'Export Player Data', - 'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings." + 'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.", + 'settings.gameNotice': "These only apply to Mai and Wacca." } export const EN_REF_USERBOX = { diff --git a/AquaNet/src/pages/User/Settings.svelte b/AquaNet/src/pages/User/Settings.svelte index ca4f8e1b..02c8a7ec 100644 --- a/AquaNet/src/pages/User/Settings.svelte +++ b/AquaNet/src/pages/User/Settings.svelte @@ -9,6 +9,7 @@ import { pfp } from "../../libs/ui"; import { t, ts } from "../../libs/i18n"; import { FADE_IN, FADE_OUT } from "../../libs/config"; + import Cropper from "svelte-easy-crop"; import UserBox from "../../components/settings/ChuniSettings.svelte"; import Mai2Settings from "../../components/settings/Mai2Settings.svelte"; import WaccaSettings from "../../components/settings/WaccaSettings.svelte"; @@ -32,6 +33,11 @@ // Fetch user data const getMe = () => USER.me().then((m) => { + if (pfpCropURL != null) { + URL.revokeObjectURL(pfpCropURL); + pfpField.value = ""; + pfpCropURL = null; + } me = m CARD.userGames(m.username).then(games => { @@ -50,6 +56,8 @@ let changed: string[] = [] let pfpField: HTMLInputElement + let pfpCropURL: string | null = null; + let pfpCrop = { width: 0, height: 0, x: 0, y: 0 }; function submit(field: string, value: string) { if (submitting) return @@ -60,16 +68,53 @@ }).catch(e => error = e.message).finally(() => submitting = "") } - function uploadPfp(file: File) { + function uploadPfp() { if (submitting) return - submitting = 'profilePicture' - - USER.uploadPfp(file).then(() => { - me.profilePicture = file.name - // reload - getMe() - }).catch(e => error = e.message).finally(() => submitting = "") + // Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + canvas.width = 256; + canvas.height = 256; + let img = document.createElement("img"); + img.onload = () => { + ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, 256, 256); + canvas.toBlob(blob => { + if (!blob) return; + submitting = 'profilePicture' + USER.uploadPfp(blob as File).then(() => { + me.profilePicture = me.username + // reload + // this doesn't work btw + setTimeout(getMe, 200); + }).catch(e => error = e.message).finally(() => submitting = "") + }); + } + img.src = pfpCropURL ?? ""; } + function handlePfpUpload(e: Event & { target: HTMLInputElement }) { + if (!e.target) return; + let files = e?.target?.files; + if (!files || files.length <= 0) return; + let file = files[0]; + console.log(me.username, me); + switch (file.type) { + case "image/gif": + USER.uploadPfp(file).then(() => { + me.profilePicture = me.username + // reload + setTimeout(getMe, 200); + }).catch(e => error = e.message).finally(() => submitting = "") + break; + case "image/png": + case "image/jpg": + case "image/jpeg": + case "image/webp": + pfpCropURL = URL.createObjectURL(file); + break; + default: + error = t("settings.profile.bad-format"); + } + }; const passwordAction = (node: HTMLInputElement, whether: boolean) => { if (whether) node.type = 'password' @@ -107,8 +152,9 @@ {/if}
+ pfpField.files && uploadPfp(pfpField.files[0])} /> + on:change={handlePfpUpload} />
{#each profileFields as [field, name], i (field)} @@ -155,6 +201,22 @@ +{#if pfpCropURL != null} +
+
+
+ pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"> +
+ + +
+
+{/if} +