mirror of https://github.com/hykilpikonna/AquaDX
feat: ➕ pfp cropper + enhancements
parent
7d214ba214
commit
16112d4f1d
|
@ -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"
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
img
|
||||
width: 1.5rem
|
||||
height: 1.5rem
|
||||
border-radius: 50%
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
|
||||
.pfp
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue