feat: pfp cropper + enhancements

pull/111/head
Raymond 2025-01-16 19:56:44 -05:00 committed by Azalea
parent 7d214ba214
commit 16112d4f1d
5 changed files with 83 additions and 13 deletions

View File

@ -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"

View File

@ -75,7 +75,7 @@
img
width: 1.5rem
height: 1.5rem
border-radius: 50%
border-radius: vars.$border-radius
object-fit: cover
.pfp

View File

@ -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 @@
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
<p class="warning">
These settings affect Mai and Wacca.
{ts("settings.gameNotice")}
</p>
<GameSettingFields game="general"/>
<div class="field">

View File

@ -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 = {

View File

@ -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 @@
</button>
{/if}
</div>
<!-- Genuinely don't know why this is giving me an intellisense error. Works fine. -->
<input id="profile-upload" type="file" accept="image/*" style="display: none" bind:this={pfpField}
on:change={() => pfpField.files && uploadPfp(pfpField.files[0])} />
on:change={handlePfpUpload} />
</div>
{#each profileFields as [field, name], i (field)}
@ -155,6 +201,22 @@
<StatusOverlays {error} loading={!me || !!submitting} />
</main>
{#if pfpCropURL != null}
<div class="overlay" transition:fade>
<div>
<div class="cropper-container">
<Cropper maxZoom={1e9} oncropcomplete={(e) => pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"></Cropper>
</div>
<button on:click={uploadPfp}>
{t("settings.profile.save")}
</button>
<button on:click={getMe}>
{t("back")}
</button>
</div>
</div>
{/if}
<style lang="sass">
@use "../../vars"
@ -196,5 +258,10 @@
max-height: 100px
border-radius: vars.$border-radius
object-fit: cover
aspect-ratio: 1
.cropper-container
position: relative
width: 400px
aspect-ratio: 1
</style>